Storyline 3 – Menu personnalisé

La problématique

Un casse-tête que je rencontre régulièrement dans la création et l’intégration de modules sous Storyline est la personnalisation du menu.

En effet, certains clients souhaitent soit avoir un menu personnalisé, soit un menu pour un module à embranchements, etc. Peu importe la problématique initiale, la solution est toujours la même : refaire un menu à la main sur un calque du masque principal.
Un travail assez long et fastidieux selon le nombre de page du module et parfois compliqué selon les contraintes (comme un menu restreint par exemple).

Il serait bien plus pratique de pouvoir utiliser le menu généré directement par Storyline. C’est pourquoi j’ai développé ce script qui va nous permettre de récupérer le menu et d’en faire à peu près tout ce qu’on veut !

Le concept

L’idée est assez simple, la réalisation beaucoup moins : attraper le menu dans le lecteur et le déplacer dans le contenu. Grâce à un script javascript, nous allons déplacer le bloc du menu pour venir le mettre dans un élément du contenu et le styliser grâce à du CSS. Il va également être possible de définir une chaîne de caractère dans le menu afin de le découper (par exemple si on souhaite un menu en plusieurs langues ou si le module propose plusieurs branches selon le niveau des apprenants).

Le résultat

Voyons déjà le résultat final du script :

Le menu affiché au centre du module est bien celui de Storyline, il est paramétré en restreint et n’affiche que la partie française.

Il peut être ouvert grâce au menu burger ou au lien (personnalisé) dans le lecteur. Il se ferme en cliquant sur le bouton en dessous ou bien en recliquant sur le lien du lecteur (ou lorsqu’on change de page, soit via les boutons de navigation, soit en ayant cliqué sur un lien du menu).

Les fichiers sources (archive 7zip, 381 ko) :

Avertissement

Le script étant contenu dans un objet web, il ne fonctionnera pas (sans aide) en local. Toutefois, grâce au projet Laragon qui va créer un serveur local (beaucoup plus facilement que EsayPHP par exemple), il est possible de le tester assez simplement.

Pour cela, téléchargez la version portable puis décompressez là. Pour tester votre module, il vous suffit de copier son export (web, scorm, etc) dans le dossier « www », puis exécutez le fichier « laragon.exe » (Windows devrait vous demander quelques autorisations à accepter), cliquez ensuite sur le bouton « Démarrer » en bas à gauche. Enfin, cliquez sur le bouton « Web » qui vous ouvrira une page avec la liste des fichiers de votre export, il ne vous restera qu’à cliquer sur le fichier « story.html ».

La préparation du module

Le lecteur

Les paramètres du lecteur

Dans Storyline, il va falloir préparer quelques éléments. Tout d’abord, au niveau du lecteur, il faut activer le menu (et lui appliquer les paramètres que vous souhaitez) dans la barre supérieur et ajouter un lien personnalisé qui remplacera le lien original si vous souhaitez avoir un lien dans le lecteur, ce qui n’est pas obligatoire. Dans mon exemple, j’ai nommé mon lien personnalisé « Menu », comme l’original. Ne placez pas votre menu dans la barre latérale, cela n’aurait pas de sens puisqu’il va être déplacé dans le module et la barre latérale serait alors vide.

Il faut lui attribuer une action « exécuter le javascript » et y placer le script suivant :

var player = GetPlayer();
player.SetVar('summary',true);

Ce script va tout simplement passer la variable « summary » à « true » et ainsi déclencher l’affichage du menu.

Si vous devez découper votre menu, il faudra insérer un nouvel entête avec une chaîne de caractère personnalisée à l’endroit où vous souhaitez réaliser la ou les césures. Dans mon exemple, j’ai choisi la chaîne « // » car elle est rarement utilisée dans un texte mais vous pouvez choisir ce que vous voulez.

Les paramètres du menu

Les variables

Les variables nécessaires

Il va ensuite falloir créer au moins une variable :

  1. « summary » : une booléenne qui va nous servir à ouvrir et fermer le menu (vous pouvez changer son nom mais il faudra également le remplacer dans le script) ;
  2. si votre menu est découpé (comme dans mon exemple avec « // »), il faudra également une variable à tester pour savoir quelle partie du menu est à afficher à quel moment. Dans mon exemple, j’ai créé la variable texte « langue » qui peut prendre les valeurs « FR » ou « EN ». Lorsque la valeur est à « FR », le menu en français s’affiche et lorsqu’elle est à « EN », c’est le menu en anglais. Il me serait donc relativement facile de rajouter des langues si nécessaire.

Le masque

Afin que le menu soit accessible de n’importe où, il convient de le créer sur un calque du masque principal.
On ajoutera également les différents éléments de navigation nécessaires (à définir selon vos contraintes) comme le menu burger pour ouvrir le menu. Le bouton d’ouverture du menu n’est pas obligatoirement sur le masque principal ; comme le menu s’ouvrira au changement d’état de la variable « summary », ce bouton peut être placé n’importe où. Nous lui ajoutons donc un déclencheur pour affecter à la variable « summary » la valeur « Vrai » au clic.

Dans les déclencheurs, il faut initialiser la variable « summary » à « Faux » lorsque la chronologie de la diapositive commence afin de s’assurer qu’elle soit bien à son état par défaut et cohérente avec l’état du menu à l’entrée sur une page.

Il faudra également ajouter un petit script qui masquera le lien original du menu dans le lecteur (nous le détaillerons plus loin).

Enfin, le déclencheur qui va « écouter » la variable « summary » et afficher le calque du menu en conséquence.

Déclencheur pour ouvrir le menu

Ajoutons à présent le calque qui recevra le menu, dans un soucis d’originalité, je l’ai nommé « menu ».

Commençons par préparer les éléments de notre calque. J’ai ajouté un rectangle bleu foncé avec un peu de transparence (pour l’effet de superposition), un cadre blanc, un bouton pour fermer le menu (la croix blanche dans un cercle) et un bloc bleu qui recevra le menu avec le texte « Menu » pour le reconnaître facilement et le texte alternatif « Menu » (également), ce texte alternatif est important, il nous permettra de détecter l’élément dans lequel déposer le menu. Vous pourrez personnaliser ce texte, mais il faudra là encore le modifier dans le script, je vous conseille toutefois un nom explicite car il pourra être lu par lecteurs d’écran.

Il est temps à présent d’intégrer le script qui déplacera le module dans le contenu. Pour cela, nous ajoutons un objet web en ciblant le dossier du script contenu dans l’archive téléchargeable (« custom-menu »).

Comme le script mettra un dixième de seconde à se charger, il vaut mieux afficher les objets du menu avec un léger décalage, je les donc placé à 0,25 s sur la chronologie du calque.

Dans les propriétés d’accessibilité (raccourci ctrl+maj+entrée) du bouton pour fermer le menu, il faut ajouter le texte alternatif « close-menu », afin de pouvoir l’identifier dans le script.

Paramètres du bouton « Fermer »

Sur le bouton, nous appliquons également un déclencheur pour passer la variable « summary » à « Faux », ce qui aura donc pour conséquence de fermer le calque.

Nous ajoutons également le déclencheur qui « écoute » la variable « summary » mais cette fois afin de masquer le calque.

Et enfin un déclencheur pour le code JavaScript des options :

var player = GetPlayer();
var options = {
general :{
  branch       : player.GetVar('langue'), //Leave empty '' if none
  menuId       : 'Menu', //Text content of the SL element
  btnCloseId : 'close-menu', //Alternative text of the button
  cutExp       : '//', //The expression to look after to cut the menu
  branches     : ['FR','EN'], //The different menu parts
  menuTitle    : ['Menu','Contents'] //The title of the menu button in the player nav bar (if there is one)
},
menu:{
  restriction  : 'free', //If the menu is "free", "restricted" or "locked"
  collapsible  : false //If the chapters can collapse
},
menuDesign:{
  background : 'rgba(255,255,255,0)',
  indent     : '20px'
},
chapterDesign:{
  fontSize   : '2vmin',
  color      : 'rgba(8,109,202,1)',
  fontWeight : 'bold',
  padding    : 0
},
slideDesign:{
  fontSize         : '1.8vmin',
  color            : 'rgba(200,200,200,1)',
  colorActive      : 'rgba(75,172,198,1)',
  colorHover       : 'rgba(75,172,198,1)',
  backgroundHover  : 'rgba(225,225,225,1)',
  colorViewed      : 'rgba(0,76,146,1)',
  backgroundViewed : 'rgba(200,220,240,1)',
  fontWeight       : 'normal',
  padding          : '0 0 0 10px',
  before           : 'content : "➧"',
  beforeIE         : 'content : "➧"' //If the previous doesn't display on IE
}
};
window.options = options;

« branch » correspond à la variable qui doit être utilisée pour découper le module (si c’est le cas). Ici, elle renverra « FR » ou « EN » selon que nous nous trouvons dans la partie française ou anglaise du module, à laisser vide(«  ») si il n’y a pas d’embranchement
« menuId » correspond au texte renseigner dans le bloc (bleu dans mon exemple) devant recevoir le menu. Dans notre exemple, nous avons choisi « Menu », nous devons donc le reporter ici.
« btnCloseId » correspond au texte alternatif entré dans les options d’accessibilité du bouton de fermeture du menu, ici : « close-menu ».
« cutExp » correspond à la chaîne de caractère qui servira à diviser le sommaire si besoin.
« branches » est un tableau listant les différents embranchements possible du module, ici : « FR » et « EN », à laisser vide ([]) si non utile .
« menuTitle » indique le titre que l’on a donné au lien personnalisé du menu dans le lecteur si on en a rajouté un.

« restriction » précise le type de navigation du menu : « free », « restricted » ou « locked » (il faut le préciser au script car je n’ai pas réussi à le déterminer de façon automatique).
« collapsible » indique si le menu peut être réduit ou non en cliquant sur les titres des scènes (si l’option a été activée dans le lecteur).

Les options sont à personnaliser selon votre projet (si il y a des embranchements, si le menu est libre, restreint ou verrouillé, etc).

Pour cette partie, il faut connaître un peu le langage CSS afin de pouvoir ajouter des règles. Les noms des options sont arbitraires, il est possible d’en ajouter ou d’en supprimer, elles seront utilisées tout à la fin du script. Pour la grosseur du texte, je vous conseille d’utiliser l’unité « vmin » afin qu’il se redimensionne avec la taille de la fenêtre.

Pour les options de design vous pouvez vous aider de la documentation (https://developer.mozilla.org/fr/docs/Web/CSS/Reference), mais les principaux changements seront surtout la taille du texte et les couleurs.

Le module est à présent paramétré, nous pouvons nous intéresser aux scripts.

Les scripts

Tout d’abord, il faut commencer par masquer le lien officiel du menu du lecteur. Pour cela nous allons injecter un peu de CSS via un script JavaScript dans la page du masque principal afin de s’assurer qu’elle soit bien injectée si l’on reprend le module après l’avoir fermé.

Nous créons d’abord la balise <style> :

var sheet = (function() {
	if(!document.querySelector('#custom-menu-display')){
		// Create the <style> tag
		var style = document.createElement("style");
		style.type = 'text/css';
		style.id = 'custom-menu-display';
		
		// WebKit hack :(
		style.appendChild(document.createTextNode(""));

		// Add the <style> element to the page
		document.head.appendChild(style);

		return style.sheet;
	}
})();

Puis nous ajoutons la règle CSS qui masquera le lien du menu :

//Hide the menu item
sheet.insertRule('#outline-link{display:none !important;}',0);

Enfin, nous déplaçons le lien en première place (même si il est masqué) afin qu’il ne génère pas un séparateur qui n’a plus lieu d’être :

//Append the link menu at the first place to hide the separator
document.querySelector('#outline-link').parentNode.insertAdjacentElement('afterbegin', document.querySelector('#outline-panel'));
document.querySelector('#outline-link').parentNode.insertAdjacentElement('afterbegin', document.querySelector('#outline-link'));

Passons à présent au script principal.

Passons à présent aux explications du script lui-même pour celles et ceux qui souhaitent comprendre comment il fonctionne ; avec les indications précédentes vous devriez avoir toutes les information pour utiliser le script.

À partir d’ici je serais un peu plus succinct dans mes explications considérant que je m’adresse à un public un peu plus avancé en développement JavaScript.

Tout d’abord, le script définie la fonction et récupére l’API de Storyline afin de pouvoir accéder aux variables (« langue » et « summary ») puis il initialise les options :

var customMenu = function(p){
//Get the SL player 
var player = p.GetPlayer();

var options = p.options;

La variable p devant options fait référence au parent de notre script, c’est à dire, notre module.

Le script commence par récupérer le lien personnalisé du lecteur si on en a ajouté un car il devra lui ajouter une action au clic plus loin :

//The script core /////////////////////////////////////////////

//Find the summary button in the player nav
var customButtons = p.document.querySelectorAll(".cs-tab");
var btnNavMenu;
if(customButtons.length>0){
	for(var i=0; i<customButtons.length; i++){
		if(options.general.menuTitle.indexOf(customButtons[i].textContent) > -1 && customButtons[i].parentNode.parentNode.classList.contains('custom-link')){
			btnNavMenu = customButtons[i].parentNode.parentNode;
		}
	}
}

Il vérifie ensuite le navigateur car il y aura des petits ajustements à faire si il s’agit de IE11…

//Detect if the brower is IE
function GetIEVersion() {
  var sAgent = window.navigator.userAgent;
  var Idx = sAgent.indexOf("MSIE");

  // If IE, return version number.
  if (Idx > 0) 
    return parseInt(sAgent.substring(Idx+ 5, sAgent.indexOf(".", Idx)));

  // If IE 11 then look for Updated user agent string.
  else if (!!navigator.userAgent.match(/Trident\/7\./)) 
    return 11;

  else
    return 0; //It is not IE
}

Il ajoute ensuite à la page (sur la balise <body>) une class correspondant à la branche dans laquelle je me trouve si on l’a défini dans les options et supprime la class d’une branche précédemment ajoutée afin d’éviter tout conflit :

//Add class branch to the page
for (var i=0; i<options.general.branches.length;i++){
	p.document.body.classList.remove(options.general.branches[i]);
}
if(options.general.branch != ""){
	p.document.body.classList.add(options.general.branch);
}

Le script récupére le conteneur dans lequel il va placer le menu (le rectangle bleu avec « Menu »), le menu du lecteur et le conteneur du menu du lecteur (pour pouvoir le replacer une fois utilisé, et le retrouver la fois suivante). Il va ensuite vider le contenu du nouveau conteneur (puisque nous ne souhaitons pas conserver le fond bleu, ni le texte « Menu ») et déplacer le menu du lecteur dans ce nouveau conteneur :

//Get get the new menu container
var menuBox = p.document.querySelector("[data-acc-text='"+options.general.menuId+"']");
menuBox.id = "menuCustom";

//Get the menu
var menu = p.document.querySelector("#outline-content");

//Get the menu container
var menuContainer = menu.parentNode;

//Empty the new menu container
menuBox.innerHTML = "";

//Grab the menu
menuBox.appendChild(menu);

Nous n’utiliserons pas la fonction clone() qui supprime tous les événements sur l’objet cloné, la seule solution étant alors de déplacer le menu selon nos besoins.

Le script récupère ensuite le bouton de fermeture du menu :

//Define the close button
var btnClose = p.document.querySelector("[data-acc-text='"+options.general.btnCloseId+"']");
if(btnClose.parentNode.classList.contains("group")){
	btnClose = p.document.querySelector("[data-acc-text='"+options.general.btnCloseId+"']").parentNode.parentNode;
}

On constate ici que la méthode n’est pas la même selon que le bouton contient un ou plusieurs objets.

Il récupère à présent les boutons « suite », « retour » et « valider » du lecteur car il devra leur ajouter une action pour fermer le menu (et donc le remettre à sa place) à cause d’un bug de IE 11 :

//Get nav and close buttons
var btnNext = p.document.getElementById('next');
var btnPrev = p.document.getElementById('prev');
var btnSubmit = p.document.getElementById('submit');

Puis il ajoute l’action au clic si ce n’est pas déjà le cas (il le vérifie avec l’attribut de donnée « reset-menu » qu’il ajoutera en même temps que l’action, normalement déjà supprimée, mais autant être prudent) :

//Add reset action to nav and close buttons
var btnsReset = [btnNext, btnPrev, btnSubmit, btnClose];
if(btnNavMenu){
	btnsReset.push(btnNavMenu)
}
for(var i=0; i < btnsReset.length; i++){
	if(!btnsReset[i].hasAttribute('data-reset-menu')){
		btnsReset[i].addEventListener('click', resetMenu);
		btnsReset[i].setAttribute('data-reset-menu',true);
	}
}

Et il crée la fonction qui replace le menu à sa place d’origine et nettoie les actions ajoutées pour éviter les doublons :

//Function to relocate the menu at its orginal place
function resetMenu(e){
	player.SetVar('summary',false);
	menuContainer.appendChild(menu);
	
	//Delete the action from all items
	var allItems = p.document.querySelectorAll("[data-reset-menu = true]");
	for(var i=0; i < allItems.length; i++){
		allItems[i].removeEventListener('click', resetMenu);
		allItems[i].removeAttribute('data-reset-menu');
	}	
}

Le script va à présent s’intéresser aux éléments du menu, il commence donc par tous les récupérer :

//Get all list items
var items = menu.querySelectorAll(".listitem");

Un bug (ou une fonctionnalité ?) de Storyline pose un petit problème lorsque nous insèrons deux fois (ou plus) la même page dans le menu (par exemple la page de choix des langues qui sera présente dans toutes les langues), seul le dernier élément aura la class « cs-viewed » une fois la page visitée (bien que lien soit cliquable, ouf !). Pour palier ce problème, le code suivant qui va vérifier si un lien est présent plusieurs fois, et, si une de ses occurrences possède la class « cs-viewed », il l’ajoutera à toutes les autres :

//Find double entries (if manual modifications of the menu)
var _aItemsViewedId = [];
for(var i=0; i<items.length; i++){
	//Get all viewed slides IDs
	if(items[i].classList.contains('cs-viewed')){
		var itemId = items[i].dataset.ref.split(".");
		_aItemsViewedId.push(itemId[2]);
	}
}
for(var i=0; i<items.length; i++){
	//Add the viewed class to the duplicate items
	var itemId = items[i].dataset.ref.split(".");
	if(_aItemsViewedId.indexOf(itemId[2]) > -1 && !items[i].classList.contains('cs-viewed')){
		items[i].classList.add('cs-viewed');
	}
}

Une petite ligne pour initialiser le comptage des découpes du menu (ici 1 découpe) :

//Initialize the cut id to add the right class to items, after the first cut, the id is set to 1, etc.
var cutId = 0;

Le script va à présent parcourir l’ensemble des éléments du menu et commencer par nettoyer leurs class et données (data-) afin de les mettre à jour avec les nouvelles informations du menu (comme les pages vues), puis il leur ajoutera les class en fonction du type d’élément (page ou chapitre), si ils sont cliquable ou non et l’action de remise à zéro du menu ; enfin il ajoutera la class qui correspond à la partie du menu où se trouve l’item (dans l’exemple, « FR » ou « EN ») :

//Customize items with classes, data and action
for (var i = 0; i < items.length; i++) {
	//Reset class and data
	items[i].classList.remove("menu-item-active");
	items[i].classList.remove("menu-no-click");
	items[i].removeAttribute("data-reset-menu");
	
	//Add class for styling
	if(items[i].hasAttribute('data-is-scene')){
		items[i].classList.add("menu-item-chapter");
		if(options.menu.collapsible === false){
			items[i].classList.add("no-collapse");
		}
		else{
			items[i].classList.add("menu-item-active");
		}
	}
	else{
		items[i].classList.add("menu-item-slide");
	}
	if(!items[i].hasAttribute('data-is-scene') && !items[i].classList.contains('cs-viewed') && options.menu.restriction === 'restricted'){
		items[i].classList.add("menu-no-click");
	}
	else if(options.menu.restriction === 'locked'){
		items[i].classList.add("menu-no-click");
	}
		
	//Add event to relocate the menu at its original place when clicking a menu item (to be available in the other pages)
	if(!items[i].hasAttribute('data-is-scene') && !items[i].classList.contains('cs-selected') && !items[i].hasAttribute('data-reset-menu') && !items[i].classList.contains('menu-no-click')){
		items[i].addEventListener('click', resetMenu);
		items[i].classList.add("menu-item-active");
		items[i].setAttribute('data-reset-menu',true);
	}
	
	//Split the menu (ie: french/english)
if(items[i].textContent.indexOf(options.general.cutExp) > -1){
		cutId ++;
		items[i].style.display = "none";
	}
items[i].classList.add(options.general.branches[cutId]);
};

Le menu est à présent déplacé et personnalisé avec les class et les actions nécessaires, il est temps de passer à la partie CSS.

Pour commencer, il faut créer la feuille de style (en vérifiant qu’elle n’existe pas déjà) :

var sheet = (function() {
	if(!p.document.querySelector('#custom-menu-style')){
		// Create the <style> tag
		var style = p.document.createElement("style");
		style.type = 'text/css';
		style.id = 'custom-menu-style';
		
		// WebKit hack :(
		style.appendChild(p.document.createTextNode(""));

		// Add the <style> element to the page
		p.document.head.appendChild(style);

		return style.sheet;
	}
})();

Les règles génériques pour le menu comme l’indentation des éléments, le curseur, etc :

//CSS for the menu
sheet.insertRule('#outline-content{ height: auto !important; width: 100% !important;}',0);
sheet.insertRule('#outline-content .listitem svg{float:left; margin-right: 2%;}',0);

//CSS items
sheet.insertRule('#outline-content li div{padding-left:0 !important;}',0);
sheet.insertRule('#outline-content li{cursor:default;}',0);
sheet.insertRule('#outline-content .menu-item-slide.menu-item-active{cursor:pointer;color : '+options.slideDesign.colorActive+' !important;}',0);
sheet.insertRule('#outline-content li{padding-left:'+options.menuDesign.indent+';}',0);

Les règles pour les éléments de page du menu, comme la taille des textes, la couleur, etc :

//CSS Slides items
sheet.insertRule('#outline-content .menu-item-slide{ font-size: '+options.slideDesign.fontSize+' !important; color : '+options.slideDesign.color+' !important; font-weight: '+options.slideDesign.fontWeight+'; padding : '+options.slideDesign.padding+' !important;}',0);
if (GetIEVersion() === 11){
	sheet.insertRule('#outline-content .menu-item-slide::before{'+options.slideDesign.beforeIE+'}',0);
}
else{
	sheet.insertRule('#outline-content .menu-item-slide::before{'+options.slideDesign.before+'}',0);
}
sheet.insertRule('#outline-content .menu-item-slide.menu-item-active.cs-viewed{color : '+options.slideDesign.colorViewed+' !important;}',0);
sheet.insertRule('#outline-content .menu-item-slide.cs-viewed{color : '+options.slideDesign.colorViewed+' !important;}',0);
sheet.insertRule('#outline-content .menu-item-slide.cs-viewed{background : '+options.slideDesign.backgroundViewed+' !important;}',0);
sheet.insertRule('#outline-content .cs-listitem:hover, #outline-content .menu-item-slide.cs-viewed:hover{background : '+options.slideDesign.backgroundHover+' !important; color:'+options.slideDesign.colorHover+' !important;}',0);

Les règles pour les éléments de chapitre :

//CSS Chapter items
sheet.insertRule('#outline-content .menu-item-chapter{ font-size: '+options.chapterDesign.fontSize+' !important; color : '+options.chapterDesign.color+' !important; font-weight: '+options.chapterDesign.fontWeight+';}',0);

Les règles pour le conteneur du menu :

//CSS for the menu container
sheet.insertRule('#menuCustom{ pointer-events: auto !important; overflow: auto !important; scrollbar-width: thin !important; background: '+options.menuDesign.background+';}',0);

Quelques règles pour ajuster un peu le style des éléments non refermables et inactifs :

//CSS no collapse
sheet.insertRule('#outline-content .listitem.no-collapse{pointer-events: none !important;}',0);
sheet.insertRule('#outline-content .listitem.no-collapse svg{display:none !important;}',0);

//CSS inactive item
sheet.insertRule('#outline-content li div.menu-no-click{pointer-events: none !important;}',0);

Les règles les plus importantes si nous avons un menu découpé qui vont afficher ou masquer les éléments correspondants à nos branches, par défaut tout le menu est masqué puis seuls les items dont la class correspond à celle de la page sont affichés :

//CSS to hide wrong langage items
for(var i = 0; i< options.general.branches.length; i++){
	sheet.insertRule(' #outline-content .listitem.'+options.general.branches[i]+'{display:none; !important}',0);
	sheet.insertRule('body.'+options.general.branches[i]+' #outline-content .listitem.'+options.general.branches[i]+'{display:block; !important}',0);
}

Et pour finir un petit hack pour l’ascenceur pour les navigateurs webkit ; et parce qu’un code sans hack IE n’est pas un vrai code, nous devons rajouter un test de IE 11 qui a quelques problèmes avec les règles qu’il ne connait pas :

if (GetIEVersion() === 0){
	//CSS for the scrollbar on Webkit
	sheet.insertRule('#menuCustom::-webkit-scrollbar{ width:8px;background: rgba(200,200,200,0.5);}',0);
	sheet.insertRule('#menuCustom::-webkit-scrollbar-thumb{ background: rgba(150,150,150,0.8); border-radius:4px;}',0);
}

Enfin, la fonction se termine et le script l’exécute avec la fenêtre parente comme argument :

}

customMenu(window.parent);

Pour allez plus loin

Contrairement à ce que j’avais écrit précédemment ici, ne stocker surtout pas la fonction dans une variable pour la réutiliser. Compte tenu de sa longueur elle saturera l’espace dédier au sotckage des variables (4096 caractères maximum en scorm 1.2, 64 000 en scorm 2004, mais certains LMS ont une limite bien plus basse malgré tout) ce qui risque de poser un certain nombre de problèmes (comme la reprise du module où l’apprenant l’avait quitté).

J’ai donc préféré stocker le code dans un objet web et les options directement dans la page.

Conclusion

Il s’agit là d’un code relativement complexe surtout pour les débutants en javascript, aussi les paramètres en début de scripts devraient être suffisants afin de personnaliser le menu. Il faut bien entendu prendre le temps de préparer son fichier Storyline afin que le script trouve tous les éléments dont il a besoin.

Pour le moment le script ne fonctionne qu’avec le lecteur classique mais je devrais rapidement l’adapter au lecteur moderne afin de pouvoir l’utiliser dans l’importe quel module.

En remaniant le code il est également possible de ne conserver que la partie qui permet de découper le module si on souhaite uniquement avoir un menu selon les différentes branches de son module sans pour autant le déplacer ou le personnaliser.

Si vous rencontrez des problèmes de fonctionnements, si vous avez constaté des erreurs ou des oublis ou si vous avez des suggestions d’amélioration, vous pouvez me contacter via linkedin : www.linkedin.com/in/gregoryfauchille, j’essaierais de vous répondre dans la mesure du possible selon mon emploi du temps.

Merci à Stackoverflow sans qui tout cela n’aurait pas été possible.


L’auteur

Grégory Fauchille

Aujourd’hui directeur artistique et graphiste multimédia, il s’appuie sur une formation classique en arts appliqués (prépa ATEP, Beaux-Arts EESI, École Pivaut) et une spécialisation en multimédia à l’École de Design Nantes atlantique (option gestion de projet). Après une première expérience de maquettiste chez Altavia, il intègre le studio de production d’E-doceo (aujourd’hui TalentSoft Learning) où il reste deux ans et demi. Il collabore aujourd’hui avec de nombreux studios de production e-learning dans la création de designs d’interface, graphismes, personnages, décors et motions design (2D et 3D) ; et également pour l’intégration de modules sous ECMG, Storyline, Rise, Mohive, etc.


Publié

dans

par

Étiquettes :