Skip to content

Formation SCORM pour Développeurs Full-Stack

🎯 Objectifs de la Formation

Cette formation technique s'adresse aux développeurs full-stack souhaitant maîtriser la création de contenus SCORM, avec un focus particulier sur : - La création de packs multi-SCO (Sharable Content Objects) - L'export STL (SCORM Test and Launch) - L'intégration avec les LMS (Learning Management Systems) - La conformité aux standards SCORM 1.2 et 2004

📚 Table des Matières

  1. Introduction au SCORM
  2. Architecture et Standards
  3. Création de Packs Multi-SCO
  4. Export STL et Conformité
  5. Intégration LMS
  6. Exemples Pratiques
  7. Bonnes Pratiques
  8. Troubleshooting

Introduction au SCORM

Qu'est-ce que le SCORM ?

SCORM (Sharable Content Object Reference Model) est un ensemble de standards techniques qui permettent aux contenus d'apprentissage d'être partagés entre différents systèmes de gestion de l'apprentissage (LMS).

Versions du SCORM

SCORM 1.2 (2001)

  • Avantages : SimplicitĂ©, large compatibilitĂ©
  • InconvĂ©nients : FonctionnalitĂ©s limitĂ©es
  • Utilisation : Projets simples, compatibilitĂ© maximale

SCORM 2004 (4ème édition - 2009)

  • Avantages : FonctionnalitĂ©s avancĂ©es, sĂ©quençage
  • InconvĂ©nients : ComplexitĂ©, compatibilitĂ© variable
  • Utilisation : Projets complexes, parcours adaptatifs

Composants Clés

SCO (Sharable Content Object)

// Structure d'un SCO
const sco = {
    identifier: "sco_001",
    title: "Introduction au JavaScript",
    launch: "index.html",
    resources: ["assets/", "scripts/", "styles/"],
    metadata: {
        description: "Module d'introduction au JavaScript",
        duration: "PT30M",
        difficulty: "beginner"
    }
};

Asset

  • Ressources statiques (images, CSS, JS, vidĂ©os)
  • Non traçables individuellement
  • RĂ©utilisables entre SCOs

Manifest

  • Fichier XML dĂ©crivant le package
  • Structure et organisation du contenu
  • MĂ©tadonnĂ©es et prĂ©requis

Architecture et Standards

Structure d'un Package SCORM

mon_package_scorm/
├── imsmanifest.xml          # Manifest principal
├── sco1/                    # Premier SCO
│   ├── index.html
│   ├── assets/
│   └── scripts/
├── sco2/                    # Deuxième SCO
│   ├── index.html
│   └── assets/
└── shared/                  # Assets partagés
    ├── styles/
    └── images/

Manifest XML (SCORM 1.2)

<?xml version="1.0" encoding="UTF-8"?>
<manifest identifier="package_001" version="1.0"
    xmlns="http://www.imsproject.org/xsd/imscp_rootv1p1p2"
    xmlns:adlcp="http://www.adlnet.org/xsd/adlcp_rootv1p2"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://www.imsproject.org/xsd/imscp_rootv1p1p2 imscp_rootv1p1p2.xsd
                        http://www.adlnet.org/xsd/adlcp_rootv1p2 adlcp_rootv1p2.xsd">

    <metadata>
        <schema>ADL SCORM</schema>
        <schemaversion>1.2</schemaversion>
        <adlcp:location>metadata.xml</adlcp:location>
    </metadata>

    <organizations default="default_org">
        <organization identifier="default_org">
            <title>Formation Développement Web</title>
            <item identifier="sco1" identifierref="sco1_ref">
                <title>Introduction HTML</title>
                <adlcp:masteryscore>80</adlcp:masteryscore>
            </item>
            <item identifier="sco2" identifierref="sco2_ref">
                <title>Introduction CSS</title>
                <adlcp:masteryscore>75</adlcp:masteryscore>
            </item>
        </organization>
    </organizations>

    <resources>
        <resource identifier="sco1_ref" type="webcontent" 
                  adlcp:scormtype="sco" href="sco1/index.html">
            <file href="sco1/index.html"/>
            <file href="sco1/scripts/scorm_api.js"/>
            <file href="sco1/styles/main.css"/>
        </resource>

        <resource identifier="sco2_ref" type="webcontent" 
                  adlcp:scormtype="sco" href="sco2/index.html">
            <file href="sco2/index.html"/>
            <file href="sco2/scripts/scorm_api.js"/>
            <file href="sco2/styles/main.css"/>
        </resource>
    </resources>
</manifest>

API SCORM

Initialisation de l'API

class SCORMAPI {
    constructor() {
        this.api = null;
        this.connected = false;
        this.init();
    }

    init() {
        // Recherche de l'API SCORM
        if (window.parent && window.parent.API_1484_11) {
            this.api = window.parent.API_1484_11;
        } else if (window.parent && window.parent.API) {
            this.api = window.parent.API;
        } else if (window.API_1484_11) {
            this.api = window.API_1484_11;
        } else if (window.API) {
            this.api = window.API;
        }

        if (this.api) {
            this.connected = this.api.Initialize("");
        }
    }

    // Méthodes de base
    getValue(element) {
        if (this.connected) {
            return this.api.GetValue(element);
        }
        return null;
    }

    setValue(element, value) {
        if (this.connected) {
            return this.api.SetValue(element, value);
        }
        return false;
    }

    commit() {
        if (this.connected) {
            return this.api.Commit("");
        }
        return false;
    }

    terminate() {
        if (this.connected) {
            this.api.Terminate("");
            this.connected = false;
        }
    }
}

Création de Packs Multi-SCO

Architecture Multi-SCO

Un pack multi-SCO permet de créer des parcours d'apprentissage complexes avec : - Séquençage : Ordre de présentation des SCOs - Prérequis : Conditions d'accès aux SCOs - Progression : Suivi de l'avancement global - Navigation : Contrôle du parcours

Structure d'un Pack Multi-SCO

// Configuration du pack multi-SCO
const multiSCOConfig = {
    packageId: "formation_dev_web",
    version: "1.0",
    title: "Formation Développement Web Complète",
    scos: [
        {
            id: "html_basics",
            title: "HTML - Les Bases",
            launch: "html/index.html",
            prerequisites: [],
            masteryScore: 80,
            maxTimeAllowed: "PT45M"
        },
        {
            id: "css_basics", 
            title: "CSS - Les Bases",
            launch: "css/index.html",
            prerequisites: ["html_basics"],
            masteryScore: 75,
            maxTimeAllowed: "PT60M"
        },
        {
            id: "javascript_basics",
            title: "JavaScript - Les Bases", 
            launch: "js/index.html",
            prerequisites: ["html_basics", "css_basics"],
            masteryScore: 85,
            maxTimeAllowed: "PT90M"
        }
    ],
    sequencing: {
        controlMode: "choice",
        flowMode: "forward_only",
        rollupRules: {
            rollupObjectiveSatisfied: true,
            rollupProgressCompletion: true
        }
    }
};

Gestion de la Progression

class MultiSCOManager {
    constructor(config) {
        this.config = config;
        this.currentSCO = null;
        this.completedSCOs = [];
        this.progress = 0;
    }

    // Vérification des prérequis
    canAccessSCO(scoId) {
        const sco = this.config.scos.find(s => s.id === scoId);
        if (!sco) return false;

        return sco.prerequisites.every(prereq => 
            this.completedSCOs.includes(prereq)
        );
    }

    // Calcul de la progression globale
    calculateProgress() {
        const totalSCOs = this.config.scos.length;
        const completed = this.completedSCOs.length;
        this.progress = Math.round((completed / totalSCOs) * 100);
        return this.progress;
    }

    // Sauvegarde de la progression
    saveProgress() {
        const progressData = {
            completedSCOs: this.completedSCOs,
            progress: this.progress,
            lastAccessed: new Date().toISOString()
        };

        localStorage.setItem('scorm_progress', JSON.stringify(progressData));
        this.api.setValue('cmi.core.lesson_status', 'incomplete');
        this.api.setValue('cmi.core.score.raw', this.progress);
        this.api.commit();
    }

    // Chargement de la progression
    loadProgress() {
        const saved = localStorage.getItem('scorm_progress');
        if (saved) {
            const progressData = JSON.parse(saved);
            this.completedSCOs = progressData.completedSCOs || [];
            this.progress = progressData.progress || 0;
        }
    }
}
class SCONavigator {
    constructor(manager) {
        this.manager = manager;
        this.createNavigation();
    }

    createNavigation() {
        const nav = document.createElement('div');
        nav.className = 'sco-navigation';

        this.config.scos.forEach((sco, index) => {
            const button = document.createElement('button');
            button.textContent = sco.title;
            button.disabled = !this.manager.canAccessSCO(sco.id);

            if (this.manager.completedSCOs.includes(sco.id)) {
                button.classList.add('completed');
            }

            button.addEventListener('click', () => {
                this.launchSCO(sco.id);
            });

            nav.appendChild(button);
        });

        document.body.appendChild(nav);
    }

    launchSCO(scoId) {
        const sco = this.config.scos.find(s => s.id === scoId);
        if (sco && this.manager.canAccessSCO(scoId)) {
            // Sauvegarde avant changement
            this.manager.saveProgress();

            // Lancement du SCO
            window.location.href = sco.launch;
        }
    }
}

Export STL et Conformité

Qu'est-ce que l'Export STL ?

L'Export STL (SCORM Test and Launch) est un processus de validation et de test qui garantit : - La conformité aux standards SCORM - La compatibilité avec les LMS - La qualité technique du package - La traçabilité des données

Outils de Validation

SCORM Cloud (Rustici)

# Installation de l'outil de validation
npm install -g scorm-cloud-validator

# Validation d'un package
scorm-validate ./mon_package_scorm/

ADL Conformance Test Suite

# Test de conformité SCORM 1.2
adl-test-suite --version 1.2 --package ./mon_package_scorm/

# Test de conformité SCORM 2004
adl-test-suite --version 2004 --package ./mon_package_scorm/

Script d'Export STL Automatisé

const fs = require('fs');
const path = require('path');
const { execSync } = require('child_process');

class SCORMExporter {
    constructor(packagePath, outputPath) {
        this.packagePath = packagePath;
        this.outputPath = outputPath;
        this.validationResults = [];
    }

    // Validation du manifest
    validateManifest() {
        const manifestPath = path.join(this.packagePath, 'imsmanifest.xml');

        if (!fs.existsSync(manifestPath)) {
            throw new Error('Manifest non trouvé');
        }

        // Validation XML
        const manifest = fs.readFileSync(manifestPath, 'utf8');
        const xmlValidation = this.validateXML(manifest);

        // Validation SCORM
        const scormValidation = this.validateSCORMStructure(manifest);

        return {
            xml: xmlValidation,
            scorm: scormValidation,
            valid: xmlValidation.valid && scormValidation.valid
        };
    }

    // Test de lancement
    testLaunch() {
        const scos = this.extractSCOs();
        const results = [];

        scos.forEach(sco => {
            try {
                // Test de chargement
                const launchTest = this.testSCOLaunch(sco);
                results.push({
                    sco: sco.id,
                    launch: launchTest.success,
                    errors: launchTest.errors
                });
            } catch (error) {
                results.push({
                    sco: sco.id,
                    launch: false,
                    errors: [error.message]
                });
            }
        });

        return results;
    }

    // Génération du rapport STL
    generateSTLReport() {
        const manifestValidation = this.validateManifest();
        const launchTests = this.testLaunch();

        const report = {
            timestamp: new Date().toISOString(),
            package: this.packagePath,
            manifest: manifestValidation,
            launchTests: launchTests,
            overallStatus: this.calculateOverallStatus(manifestValidation, launchTests),
            recommendations: this.generateRecommendations(manifestValidation, launchTests)
        };

        const reportPath = path.join(this.outputPath, 'stl-report.json');
        fs.writeFileSync(reportPath, JSON.stringify(report, null, 2));

        return report;
    }

    // Export du package validé
    exportValidatedPackage() {
        const report = this.generateSTLReport();

        if (report.overallStatus === 'PASS') {
            const exportPath = path.join(this.outputPath, 'validated-package.zip');
            this.createZipPackage(exportPath);
            console.log(`Package validé exporté vers : ${exportPath}`);
        } else {
            console.log('Package non conforme, voir le rapport STL pour les détails');
        }

        return report;
    }
}

Métadonnées de Conformité

<!-- Exemple de métadonnées complètes pour l'export STL -->
<metadata>
    <schema>ADL SCORM</schema>
    <schemaversion>1.2</schemaversion>

    <!-- Informations générales -->
    <title>Formation Développement Web - Pack Multi-SCO</title>
    <description>Formation complète en développement web avec HTML, CSS et JavaScript</description>
    <keywords>développement, web, HTML, CSS, JavaScript, SCORM</keywords>

    <!-- Informations techniques -->
    <technical>
        <format>text/html</format>
        <size>15728640</size>
        <location>index.html</location>
        <requirement>
            <type>browser</type>
            <name>Chrome</name>
            <minimum_version>90.0</minimum_version>
        </requirement>
    </technical>

    <!-- Informations pédagogiques -->
    <educational>
        <interactivitytype>active</interactivitytype>
        <learningresourcetype>course</learningresourcetype>
        <interactivitylevel>high</interactivitylevel>
        <semanticdensity>medium</semanticdensity>
        <intendedenduserrole>learner</intendedenduserrole>
        <context>professional</context>
        <typicalagerange>18-65</typicalagerange>
        <difficulty>intermediate</difficulty>
        <typicallearningtime>
            <datetime>PT180M</datetime>
        </typicallearningtime>
    </educational>

    <!-- Informations de conformité -->
    <conformance>
        <scorm_version>1.2</scorm_version>
        <api_support>true</api_support>
        <sequencing_support>false</sequencing_support>
        <rollup_support>true</rollup_support>
        <stl_validated>true</stl_validated>
        <validation_date>2024-01-15T10:30:00Z</validation_date>
    </conformance>
</metadata>

Intégration LMS

Configuration LMS

Moodle

// Configuration SCORM dans Moodle
$scorm_config = [
    'scormtype' => 'local',
    'packageurl' => 'https://lms.example.com/scorm/package.zip',
    'maxgrade' => 100,
    'grademethod' => SCORM_GRADEHIGHEST,
    'maxattempt' => 3,
    'whatgrade' => SCORM_GRADEHIGHEST,
    'forcenewattempt' => SCORM_FORCENEWATTEMPT,
    'lastattemptlock' => SCORM_LOCKONFINAL,
    'masteryoverride' => SCORM_MASTERYOVERRIDE,
    'displaycoursestructure' => 1,
    'displayattemptstatus' => 1,
    'displaycoursestructure' => 1,
    'popup' => 1,
    'width' => 1000,
    'height' => 600,
    'window' => 'popup'
];

Canvas

// Configuration SCORM dans Canvas
const canvasSCORMConfig = {
    external_tool: {
        name: "SCORM Package",
        url: "https://canvas.example.com/scorm/launch",
        consumer_key: "scorm_consumer_key",
        shared_secret: "scorm_shared_secret",
        privacy_level: "public",
        custom_fields: {
            scorm_package_id: "package_001",
            scorm_version: "1.2",
            max_attempts: 3,
            mastery_score: 80
        }
    }
};

API d'Intégration

class LMSIntegration {
    constructor(lmsType, config) {
        this.lmsType = lmsType;
        this.config = config;
        this.api = this.initializeAPI();
    }

    initializeAPI() {
        switch(this.lmsType) {
            case 'moodle':
                return new MoodleAPI(this.config);
            case 'canvas':
                return new CanvasAPI(this.config);
            case 'blackboard':
                return new BlackboardAPI(this.config);
            default:
                throw new Error('LMS non supporté');
        }
    }

    // Upload du package SCORM
    async uploadPackage(packagePath) {
        const formData = new FormData();
        formData.append('package', fs.createReadStream(packagePath));
        formData.append('scorm_version', '1.2');
        formData.append('auto_launch', 'true');

        const response = await this.api.uploadSCORM(formData);
        return response.package_id;
    }

    // Configuration du cours
    async configureCourse(packageId, courseConfig) {
        const courseData = {
            package_id: packageId,
            title: courseConfig.title,
            description: courseConfig.description,
            max_attempts: courseConfig.maxAttempts || 3,
            mastery_score: courseConfig.masteryScore || 80,
            due_date: courseConfig.dueDate,
            available_from: courseConfig.availableFrom,
            available_until: courseConfig.availableUntil
        };

        return await this.api.createCourse(courseData);
    }

    // Récupération des rapports
    async getReports(courseId) {
        const reports = await this.api.getSCORMReports(courseId);
        return this.formatReports(reports);
    }

    formatReports(rawReports) {
        return rawReports.map(report => ({
            learner: report.learner_name,
            sco: report.sco_title,
            score: report.score,
            status: report.completion_status,
            timeSpent: report.total_time,
            attempts: report.attempt_count,
            lastAccess: report.last_access_date
        }));
    }
}

Exemples Pratiques

Exemple 1 : Pack Multi-SCO Simple

<!-- sco1/index.html - Introduction HTML -->
<!DOCTYPE html>
<html lang="fr">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Introduction HTML</title>
    <script src="../shared/scripts/scorm_api.js"></script>
    <link rel="stylesheet" href="../shared/styles/main.css">
</head>
<body>
    <div class="sco-container">
        <header>
            <h1>Introduction au HTML</h1>
            <div class="progress-bar">
                <div class="progress" id="progress"></div>
            </div>
        </header>

        <main>
            <section class="content">
                <h2>Qu'est-ce que le HTML ?</h2>
                <p>Le HTML (HyperText Markup Language) est le langage de balisage standard pour créer des pages web.</p>

                <div class="interactive-content">
                    <h3>Exercice Pratique</h3>
                    <div class="code-editor">
                        <textarea id="html-code" placeholder="Écrivez votre code HTML ici...">
<!DOCTYPE html>
<html>
<head>
    <title>Ma première page</title>
</head>
<body>
    <h1>Bonjour le monde !</h1>
</body>
</html>
                        </textarea>
                    </div>
                    <button onclick="validateHTML()">Valider</button>
                    <div id="validation-result"></div>
                </div>
            </section>

            <section class="quiz">
                <h3>Quiz de Compréhension</h3>
                <div class="question">
                    <p>Quelle balise HTML est utilisée pour créer un titre principal ?</p>
                    <label><input type="radio" name="q1" value="a"> &lt;title&gt;</label>
                    <label><input type="radio" name="q1" value="b"> &lt;h1&gt;</label>
                    <label><input type="radio" name="q1" value="c"> &lt;header&gt;</label>
                </div>
                <button onclick="submitQuiz()">Soumettre</button>
            </section>
        </main>

        <footer>
            <button onclick="previousSCO()" id="prev-btn" disabled>Précédent</button>
            <button onclick="nextSCO()" id="next-btn">Suivant</button>
        </footer>
    </div>

    <script>
        const scorm = new SCORMAPI();
        let currentProgress = 0;
        let quizScore = 0;

        // Initialisation
        window.onload = function() {
            loadProgress();
            updateProgress();
        };

        // Sauvegarde avant fermeture
        window.onbeforeunload = function() {
            saveProgress();
        };

        function loadProgress() {
            const saved = scorm.getValue('cmi.core.lesson_status');
            if (saved === 'completed') {
                currentProgress = 100;
                document.getElementById('next-btn').disabled = false;
            }
        }

        function updateProgress() {
            const progressBar = document.getElementById('progress');
            progressBar.style.width = currentProgress + '%';
        }

        function validateHTML() {
            const code = document.getElementById('html-code').value;
            const result = document.getElementById('validation-result');

            // Validation simple (à améliorer avec une vraie validation HTML)
            if (code.includes('<h1>') && code.includes('</h1>')) {
                result.innerHTML = '<span class="success">âś“ Code HTML valide !</span>';
                currentProgress = Math.max(currentProgress, 50);
                updateProgress();
            } else {
                result.innerHTML = '<span class="error">✗ Code HTML invalide. Vérifiez vos balises.</span>';
            }
        }

        function submitQuiz() {
            const answer = document.querySelector('input[name="q1"]:checked');
            const result = document.getElementById('validation-result');

            if (answer && answer.value === 'b') {
                result.innerHTML = '<span class="success">✓ Bonne réponse !</span>';
                quizScore = 100;
                currentProgress = Math.max(currentProgress, 100);
                updateProgress();
                document.getElementById('next-btn').disabled = false;
            } else {
                result.innerHTML = '<span class="error">✗ Mauvaise réponse. La bonne réponse est &lt;h1&gt;.</span>';
            }
        }

        function saveProgress() {
            scorm.setValue('cmi.core.lesson_status', currentProgress === 100 ? 'completed' : 'incomplete');
            scorm.setValue('cmi.core.score.raw', currentProgress);
            scorm.setValue('cmi.core.total_time', 'PT15M');
            scorm.commit();
        }

        function nextSCO() {
            saveProgress();
            window.location.href = '../css/index.html';
        }

        function previousSCO() {
            // Pas de SCO précédent dans cet exemple
        }
    </script>
</body>
</html>

Exemple 2 : Gestionnaire de Progression Avancé

// shared/scripts/progress_manager.js
class AdvancedProgressManager {
    constructor() {
        this.scorm = new SCORMAPI();
        this.scoData = {
            currentSCO: this.getCurrentSCO(),
            startTime: new Date(),
            interactions: [],
            objectives: [],
            progress: 0
        };
    }

    // Suivi des interactions
    trackInteraction(id, type, description, response, result) {
        const interaction = {
            id: id,
            type: type,
            description: description,
            response: response,
            result: result,
            timestamp: new Date().toISOString()
        };

        this.scoData.interactions.push(interaction);
        this.saveInteraction(interaction);
    }

    // Gestion des objectifs
    setObjective(objectiveId, score, status) {
        const objective = {
            id: objectiveId,
            score: score,
            status: status,
            timestamp: new Date().toISOString()
        };

        this.scoData.objectives.push(objective);
        this.saveObjective(objective);
    }

    // Calcul de la progression
    calculateProgress() {
        const totalObjectives = this.scoData.objectives.length;
        const completedObjectives = this.scoData.objectives.filter(obj => 
            obj.status === 'completed' || obj.status === 'passed'
        ).length;

        this.scoData.progress = totalObjectives > 0 ? 
            Math.round((completedObjectives / totalObjectives) * 100) : 0;

        return this.scoData.progress;
    }

    // Sauvegarde complète
    saveCompleteProgress() {
        const progress = this.calculateProgress();
        const timeSpent = this.calculateTimeSpent();

        // Sauvegarde SCORM
        this.scorm.setValue('cmi.core.lesson_status', 
            progress === 100 ? 'completed' : 'incomplete');
        this.scorm.setValue('cmi.core.score.raw', progress);
        this.scorm.setValue('cmi.core.total_time', timeSpent);
        this.scorm.setValue('cmi.core.entry', 'resume');

        // Sauvegarde des interactions
        this.scoData.interactions.forEach((interaction, index) => {
            this.scorm.setValue(`cmi.interactions.${index}.id`, interaction.id);
            this.scorm.setValue(`cmi.interactions.${index}.type`, interaction.type);
            this.scorm.setValue(`cmi.interactions.${index}.description`, interaction.description);
            this.scorm.setValue(`cmi.interactions.${index}.learner_response`, interaction.response);
            this.scorm.setValue(`cmi.interactions.${index}.result`, interaction.result);
        });

        this.scorm.commit();
    }

    calculateTimeSpent() {
        const now = new Date();
        const diff = now - this.scoData.startTime;
        const minutes = Math.floor(diff / 60000);
        return `PT${minutes}M`;
    }
}

Bonnes Pratiques

Développement SCORM

1. Structure du Code

// Organisation recommandée des fichiers
const scormStructure = {
    'shared/': {
        'scripts/': [
            'scorm_api.js',      // API SCORM de base
            'progress_manager.js', // Gestionnaire de progression
            'navigation.js',     // Navigation entre SCOs
            'utils.js'           // Utilitaires communs
        ],
        'styles/': [
            'main.css',          // Styles principaux
            'scorm.css',         // Styles spécifiques SCORM
            'responsive.css'     // Responsive design
        ],
        'assets/': [
            'images/',           // Images partagées
            'fonts/',            // Polices
            'data/'              // Données statiques
        ]
    },
    'sco1/': {
        'index.html',           // Point d'entrée
        'content.html',         // Contenu principal
        'quiz.html',            // Quiz spécifique
        'scripts/': [
            'sco1-specific.js'  // Scripts spécifiques au SCO
        ]
    }
};

2. Gestion des Erreurs

class SCORMErrorHandler {
    static handleAPIError(error, context) {
        console.error(`Erreur SCORM dans ${context}:`, error);

        // Fallback local
        this.saveToLocalStorage(context, error);

        // Notification utilisateur
        this.showUserNotification('Une erreur est survenue. Vos progrès sont sauvegardés localement.');
    }

    static saveToLocalStorage(context, data) {
        const key = `scorm_fallback_${context}`;
        const existing = JSON.parse(localStorage.getItem(key) || '[]');
        existing.push({
            timestamp: new Date().toISOString(),
            data: data
        });
        localStorage.setItem(key, JSON.stringify(existing));
    }
}

3. Performance et Optimisation

// Optimisation des appels API SCORM
class OptimizedSCORMAPI extends SCORMAPI {
    constructor() {
        super();
        this.pendingChanges = new Map();
        this.commitTimeout = null;
    }

    setValue(element, value) {
        // Mise en cache des changements
        this.pendingChanges.set(element, value);

        // Commit différé (évite trop d'appels API)
        this.scheduleCommit();
    }

    scheduleCommit() {
        if (this.commitTimeout) {
            clearTimeout(this.commitTimeout);
        }

        this.commitTimeout = setTimeout(() => {
            this.commitPendingChanges();
        }, 1000); // Commit toutes les secondes
    }

    commitPendingChanges() {
        for (const [element, value] of this.pendingChanges) {
            super.setValue(element, value);
        }
        this.pendingChanges.clear();
        super.commit();
    }
}

Tests et Validation

1. Tests Unitaires

// Tests pour les fonctionnalités SCORM
describe('SCORM API Tests', () => {
    let scormAPI;

    beforeEach(() => {
        scormAPI = new SCORMAPI();
    });

    test('should initialize API connection', () => {
        expect(scormAPI.connected).toBe(true);
    });

    test('should save progress correctly', () => {
        scormAPI.setValue('cmi.core.lesson_status', 'completed');
        scormAPI.setValue('cmi.core.score.raw', 85);

        expect(scormAPI.getValue('cmi.core.lesson_status')).toBe('completed');
        expect(scormAPI.getValue('cmi.core.score.raw')).toBe('85');
    });

    test('should handle API errors gracefully', () => {
        // Simulation d'erreur API
        window.parent.API = null;
        const newAPI = new SCORMAPI();

        expect(newAPI.connected).toBe(false);
        expect(newAPI.setValue('test', 'value')).toBe(false);
    });
});

2. Tests d'Intégration

// Tests d'intégration multi-SCO
describe('Multi-SCO Integration Tests', () => {
    test('should navigate between SCOs correctly', async () => {
        const manager = new MultiSCOManager(testConfig);
        const navigator = new SCONavigator(manager);

        // Test de navigation
        await navigator.launchSCO('sco1');
        expect(manager.currentSCO).toBe('sco1');

        await navigator.launchSCO('sco2');
        expect(manager.currentSCO).toBe('sco2');
    });

    test('should respect prerequisites', () => {
        const manager = new MultiSCOManager(testConfig);

        // SCO2 nécessite SCO1
        expect(manager.canAccessSCO('sco2')).toBe(false);

        // Marquer SCO1 comme terminé
        manager.completedSCOs.push('sco1');
        expect(manager.canAccessSCO('sco2')).toBe(true);
    });
});

Troubleshooting

Problèmes Courants

1. API SCORM Non Détectée

// Diagnostic de l'API SCORM
function diagnoseSCORMAPI() {
    const diagnosis = {
        apiFound: false,
        apiType: null,
        errors: []
    };

    try {
        if (window.parent && window.parent.API_1484_11) {
            diagnosis.apiFound = true;
            diagnosis.apiType = 'SCORM 2004';
        } else if (window.parent && window.parent.API) {
            diagnosis.apiFound = true;
            diagnosis.apiType = 'SCORM 1.2';
        } else {
            diagnosis.errors.push('Aucune API SCORM détectée');
        }
    } catch (error) {
        diagnosis.errors.push(`Erreur de détection: ${error.message}`);
    }

    return diagnosis;
}

2. Problèmes de Sauvegarde

// Diagnostic des problèmes de sauvegarde
function diagnoseSaveIssues() {
    const issues = [];

    // Vérification de la connexion API
    if (!scorm.connected) {
        issues.push('API SCORM non connectée');
    }

    // Vérification des valeurs
    const status = scorm.getValue('cmi.core.lesson_status');
    if (!status) {
        issues.push('Impossible de récupérer le statut');
    }

    // Test de sauvegarde
    const testSave = scorm.setValue('cmi.core.lesson_status', 'incomplete');
    if (!testSave) {
        issues.push('Impossible de sauvegarder les données');
    }

    return issues;
}

3. Problèmes de Navigation

// Diagnostic des problèmes de navigation
function diagnoseNavigationIssues() {
    const issues = [];

    // Vérification des SCOs
    const scos = document.querySelectorAll('[data-sco-id]');
    if (scos.length === 0) {
        issues.push('Aucun SCO trouvé dans le DOM');
    }

    // Vérification des prérequis
    const manager = new MultiSCOManager(config);
    config.scos.forEach(sco => {
        if (!manager.canAccessSCO(sco.id)) {
            issues.push(`SCO ${sco.id} non accessible (prérequis manquants)`);
        }
    });

    return issues;
}

Solutions Recommandées

1. Fallback Local

// Système de fallback pour la sauvegarde locale
class LocalFallback {
    static saveProgress(data) {
        try {
            localStorage.setItem('scorm_progress', JSON.stringify(data));
            return true;
        } catch (error) {
            console.error('Erreur de sauvegarde locale:', error);
            return false;
        }
    }

    static loadProgress() {
        try {
            const data = localStorage.getItem('scorm_progress');
            return data ? JSON.parse(data) : null;
        } catch (error) {
            console.error('Erreur de chargement local:', error);
            return null;
        }
    }

    static syncWithSCORM(scormAPI) {
        const localData = this.loadProgress();
        if (localData && scormAPI.connected) {
            // Synchronisation des données locales avec SCORM
            Object.keys(localData).forEach(key => {
                scormAPI.setValue(key, localData[key]);
            });
            scormAPI.commit();
        }
    }
}

2. Monitoring et Logs

// Système de monitoring pour le debugging
class SCORMMonitor {
    constructor() {
        this.logs = [];
        this.startMonitoring();
    }

    startMonitoring() {
        // Monitoring des appels API
        const originalSetValue = SCORMAPI.prototype.setValue;
        SCORMAPI.prototype.setValue = function(element, value) {
            this.log('API_SET_VALUE', { element, value });
            return originalSetValue.call(this, element, value);
        };

        // Monitoring des erreurs
        window.addEventListener('error', (event) => {
            this.log('ERROR', {
                message: event.message,
                filename: event.filename,
                lineno: event.lineno,
                colno: event.colno
            });
        });
    }

    log(type, data) {
        this.logs.push({
            timestamp: new Date().toISOString(),
            type: type,
            data: data
        });
    }

    getLogs() {
        return this.logs;
    }

    exportLogs() {
        const logs = this.getLogs();
        const blob = new Blob([JSON.stringify(logs, null, 2)], { type: 'application/json' });
        const url = URL.createObjectURL(blob);
        const a = document.createElement('a');
        a.href = url;
        a.download = 'scorm-logs.json';
        a.click();
    }
}

🎯 Conclusion

Cette formation vous a fourni les connaissances techniques nécessaires pour :

  • Comprendre l'architecture SCORM et ses standards
  • CrĂ©er des packs multi-SCO complexes et fonctionnels
  • Exporter des packages conformes avec validation STL
  • IntĂ©grer vos contenus avec les LMS principaux
  • DĂ©boguer et rĂ©soudre les problèmes courants

Prochaines Étapes

  1. Pratique : Créez votre premier pack multi-SCO
  2. Test : Validez avec les outils STL
  3. Déploiement : Intégrez dans un LMS de test
  4. Optimisation : Améliorez les performances et l'UX

Ressources Supplémentaires


Cette formation est un guide technique complet pour les développeurs souhaitant maîtriser la création de contenus SCORM professionnels.