• Назад

    Персоналізація навчання в Articulate Rise 360: практичний гайд від Селезень Олега

    Дата публикации: 02.10.2025
    Просмотры: 16

    Извините, этот текст доступен только на “UA” и “EN”.

    Співробітник відкриває курс і бачить звернення на своє ім’я, текст з урахуванням його гендеру, а іноді навіть свій аватар чи фото. Звучить круто, чи не так? Для слухача це начебто дрібна деталь, а для L&D фахівців – це інструмент, який суттєво підвищує залученість та ефективність навчання. Персоналізовані курси краще утримують увагу й створюють відчуття цінності: «цей курс зроблено саме для мене».

    До того ж усі дані вже зберігаються в LMS. Тож достатньо лише налаштувати так, щоб вони автоматично підвантажувались у курс.

    Разом з Олегом Селезнем ми вже ділилися досвідом використання користувацьких даних у Articulate Storyline. Тепер розглянемо інший інструмент – Articulate Rise 360, який часто обирають за простоту, швидкість і сучасний дизайн.

    У цій статті ми зосередимось на тому, як поєднати зручність Rise із можливостями персоналізації через LMS Collaborator. Своїм досвідом поділиться Олег Селезень – керівник відділу дистанційного навчання компанії VIYAR.

    Переходимо від теорії до практики: які дані з LMS Collaborator можна використати для персоналізації курсів у Rise 360?

    Олег Селезень – керівник відділу дистанційного навчання компанії VIYAR

    blank

    Які параметри ми можемо отримати?

    Усі дані передаються з LMS Collaborator у SCORM через змінну cmi.core.student_info. Їх також можна використати для персоналізації в Articulate Rise 360.

    На відміну від Storyline, у Rise 360 немає прямої роботи з тригерами чи умовами. Проте отримані значення можна застосовувати для персоналізації: налаштовувати сценарії навчання, відображати потрібні блоки та підлаштовувати звернення.

    Щоб реалізувати це, знадобиться невелика взаємодія з кодом. Але не хвилюйтеся – все максимально просто.😊

    blank

    Далі покроково, як саме це виглядає на практиці.

    Крок 1. Форматування змінних у курсі Articulate Rise 360

    Щоб підтягнути дані користувача, достатньо вказати змінну з LMS Collaborator, обрамлену знаками відсотка, наприклад:

    • user_id → %user_id%
    • user_firstname → %user_firstname%
    • user_email → %user_email%

    Коли користувач відкриває курс, система автоматично замінює значення на його справжні дані.

    Важливо: самі по собі вони не працюють. Якщо не додати код-зв’язку з LMS і не налаштувати публікацію під SCORM, у курсі залишиться просто %user_firstname%.

    Що робити далі?

      1. Опублікуйте курс у Rise 360: Publish → LMS, обираємо SCORM 1.2 або 2004 і зберігаємо ZIP.
      2. Розпакуйте архів у будь-яку папку.
      3. Знайдіть та відкрийте папку scormcontent.
      4. Знайдіть файл index.html і відкрийте його у будь-якому редакторі.
      5. Додайте ось цей код одразу після тега <title>:
    Код
    
    <script defer src="https://cdn.jsdelivr.net/gh/asimut/user-variables/selezen_v1.js"></script>
    

    Тепер курс підтягуватиме дані користувача.

    Крок 2. Фото користувача замість стандартних зображень

    Ще один простий спосіб персоналізації – заміна картинок на фото конкретного користувача. Це працює в будь-якому місці курсу, де є зображення.

    Це дає відчутну персоналізацію «в обличчя» без зміни верстки блоку та працює в будь-яких місцях курсу, де є зображення.

    Як це зробити:

    1. Відкрийте потрібний блок у Articulate Rise 360 та натисните Image Alt Text.
    2. Уведіть replace_user_img та підтвердіть.
    3. За потреби повторіть для інших зображень.

    При відкритті курсу система автоматично підставить фото користувача з LMS.

    blank

    Крок 3. Звернення за гендером

    Коли LMS передає інформацію про стать користувача (наприклад, male або female), у курсі ми можемо автоматично змінювати слова та їх закінчення відповідно до потрібного гендера.

    Приклад такого текстового блоку в Articulate Rise 360:

    blank

    На зображені стандартний текстовий блок. Саме у таких блоках можна налаштувати заміну, щоб звернення виглядали природно: «ти завершив» або «ти завершила».

    Для цього потрібно підготувати невеликий скрипт, який перевіряє отримане значення і підставляє потрібний варіант.

    На що варто звернути увагу:

    За замовчуванням усі звернення можна залишати у чоловічому роді. Потім система буде змінювати їх на потрібний варіант у тих блоках, де ви це вкажете. Важливо не підключати заміну скрізь, інакше можуть випадково змінитися слова поза контекстом звернень. Це виглядатиме як помилка.

    Як це зробити:

    1. Додаємо код, що отримує дані з LMS.
    2. Додаємо модуль для коректних звернень за гендером:

    – підключаєте основний скрипт із логікою заміни (він виконує пошук і підміну);
    – у конфігурації задаємо слова, які будуть замінюватися (наприклад, завершивзавершила) та ID блоків (targetBlocks), де ці заміни дозволені.

    У результаті система автоматично підставлятиме правильні варіанти звернень залежно від гендеру користувача.

    Код
    
    <script defer src="https://cdn.jsdelivr.net/gh/asimut/user_gender_replacer/selezen_v1.js"></script>
    
    <script>
      // Конфіг (тільки дані, ніякої логіки)
      window.GenderReplacer = {
        debug: false,
        genderReplacements: {
          female: {
            'завершив': 'завершила',
            'дізнався': 'дізналася',
            'ознайомився': 'ознайомилася',
            'зрозумів': 'зрозуміла',
            'пройшов': 'пройшла',
            'успішно завершив': 'успішно завершила',
            'готовий': 'готова',
            'вивчив': 'вивчила',
            'переглянув': 'переглянула',
            'опанував': 'опанувала',
            'засвоїв': 'засвоїла',
            'досяг': 'досягла',
            'здобув': 'здобула',
            'отримав': 'отримала',
            'виконав': 'виконала',
            'склав': 'склала',
            'закінчив': 'закінчила'
          }
        },
        targetBlocks: [
          'cmckqk51l001c357b79yaongk' 
        ]
      };
    </script>
    

    Важливо: нижній блок – це лише конфігурація (дані). Саму підміну виконує основний скрипт із логікою заміни, підключений рядком вище.

    Робота з ідентифікаторами блоків

    Щоб система знала, у якому саме текстовому блоці потрібно робити заміни, ви вказуєте його у списку targetBlocks. Для цього кожен блок у Rise має свій унікальний ID.

    Є два способи знайти ID:

    Спосіб 1.
    Через інструменти розробника DevTools. Якщо ви впевнено користуєтеся консоллю браузера, відкрийте код сторінки й скопіюйте потрібний ідентифікатор.

    Спосіб 2.
    За допомогою плагіна. Зручніше і швидше, особливо якщо не хочете працювати з кодом напряму.

    Також ви можете розширювати список слів для заміни. Якщо серед наведених прикладів немає потрібних, сміливо додавайте власні варіанти у конфігурацію.

    Щоб спростити пошук ідентифікаторів блоків, зручно користуватися плагіном.

    Як встановити плагін:

    1. Відкрийте браузер і перейдіть у розділ Розширення – Керувати розширенями.
    2. Увімкніть Режим розробника.
    3. Завантажте архів із плагіном, розпакуйте його й додайте розширення з цієї папки у браузер;
      Посилання на архів
    4. Після цього він автоматично з’явиться у вашій сторінці редагування курсу.

    Плагін можна вмикати або вимикати будь-коли. З його допомогою ви швидко знаходите ID блоків і додаєте їх у конфігурацію.

    blank

    Коли ми навчилися швидко знаходити ID за допомогою плагіна, можемо використати ці ідентифікатори не лише для точкових замін, а й для керування видимістю цілих фрагментів курсу. Це дозволить показувати різним департаментам свій контент у межах одного курсу без дублювання матеріалів і зайвих копій.

    Крок 4. Керування контентом для різних департаментів

    Ми вже маємо всі змінні з LMS, зокрема user_department. Це означає, що департамент не потрібно шукати: система зчитує його з даних користувача і на цій основі відкриває потрібні блоки курсу. Для цього потрібно лише вказати назву департаменту, який є в LMS та перелік блоків, які він має бачити. Для всіх інших працює гілка default.

    Код
    
    <script>
      (function () {
        'use strict';
      
        // Конфіг
        // Заповни mapping під свої департаменти та їхні data-block-id
        window.DepartmentBlocks = window.DepartmentBlocks || {
          debug: false,
          mapping: {
            'назава департаменту':    ['ТУТ ВКАЖИТИ ІD БЛОКУ'], // назва має буди із меленької літери
            'default':     ['ТУТ ВКАЖИТИ ІD БЛОКУ'],     // блок для всіх інших
          },
        };
      
        const DB = window.DepartmentBlocks;
      
        const ATTR_CANDIDATES = ['data-block-id','data-blockid','data-block','blockid','data-id','id'];
        const HIDE_CLASS = 'dept-hidden-by-mapping';
        const STYLE_ID = 'dept-visibility-style';
        const OBS_CONFIG = { childList: true, subtree: true, attributes: false, characterData: false };
      
        let allListedIds = new Set();      // усі ID, що зустрічаються у mapping (для первинного приховування)
        let allowedIds = new Set();        // ID, дозволені для поточного департаменту
        let currentDept = null;            // нормалізоване значення поточного департаменту
        let initialized = false;           // щоб не ініціалізувати двічі
        let mainObserver = null;
      
        const log = (...args) => { if (DB.debug) console.log('[DepartmentBlocks]', ...args); };
        const warn = (...args) => console.warn('[DepartmentBlocks]', ...args);
      
        function normalize(str) {
          return (str == null) ? '' : String(str).toLowerCase().trim();
        }
      
        function injectStyleOnce(doc = document) {
          if (doc.getElementById(STYLE_ID)) return;
          const style = doc.createElement('style');
          style.id = STYLE_ID;
          style.textContent = `
            .${HIDE_CLASS} { display: none !important; }
          `;
          doc.head.appendChild(style);
        }
      
        function getBlockIdFromElement(el) {
          for (const attr of ATTR_CANDIDATES) {
            if (el.hasAttribute && el.hasAttribute(attr)) {
              const v = el.getAttribute(attr);
              if (v && v.trim()) return v.trim();
            }
          }
          return null;
        }
      
        function collectAllListedIds() {
          allListedIds = new Set();
          const map = DB.mapping || {};
          Object.values(map).forEach(arr => {
            (arr || []).forEach(id => allListedIds.add(String(id)));
          });
          log('All listed IDs:', Array.from(allListedIds));
        }
      
        function computeAllowedIdsFor(dept) {
          const mappingKey = DB.mapping?.[dept] ? dept : 'default';
          allowedIds = new Set(DB.mapping?.[mappingKey] || []);
          log('Allowed IDs for', dept, '(using mapping key:', mappingKey, '):', Array.from(allowedIds));
        }
      
        function isListed(id) {
          return allListedIds.has(id);
        }
      
        function shouldShow(id) {
          return allowedIds.has(id);
        }
      
        function hideEl(el) {
          if (!el.classList.contains(HIDE_CLASS)) {
            el.classList.add(HIDE_CLASS);
          }
        }
      
        function showEl(el) {
          if (el.classList.contains(HIDE_CLASS)) {
            el.classList.remove(HIDE_CLASS);
          }
        }
      
        function initialHideListedBlocks(root = document) {
          const candidates = root.querySelectorAll('*');
          candidates.forEach(el => {
            const id = getBlockIdFromElement(el);
            if (id && isListed(id)) hideEl(el);
          });
        }
      
        function applyDepartmentVisibility(root = document) {
          const candidates = root.querySelectorAll('*');
          candidates.forEach(el => {
            const id = getBlockIdFromElement(el);
            if (!id) return;
      
            if (!isListed(id)) return;
            if (shouldShow(id)) showEl(el);
            else hideEl(el);
          });
        }
      
        function isUserDataReady() {
          const sources = [
            window.UserVariables?.data,
            window.UserVariables2?.data,
            window.userData
          ];
          return sources.some(d => d && typeof d === 'object');
        }
      
        function getUserDepartment() {
          if (DB.overrideDepartment) return normalize(DB.overrideDepartment);
      
          const sources = [
            window.UserVariables?.data,
            window.UserVariables2?.data,
            window.userData
          ];
          for (const d of sources) {
            if (!d || typeof d !== 'object') continue;
            for (const key of ['user_department','department','dept','user_dept','org_unit','division']) {
              if (d[key]) return normalize(d[key]);
            }
          }
          return '';
        }
      
        function processIframe(iframe) {
          try {
            const doc = iframe?.contentDocument;
            if (!doc) return;
            injectStyleOnce(doc);
            initialHideListedBlocks(doc);
            if (currentDept) applyDepartmentVisibility(doc);
            const obs = new MutationObserver(() => {
              if (!currentDept) initialHideListedBlocks(doc);
              else applyDepartmentVisibility(doc);
            });
            obs.observe(doc.documentElement, OBS_CONFIG);
          } catch (e) {}
        }
      
        function watchIframes() {
          const iframes = document.querySelectorAll('iframe');
          iframes.forEach(ifr => {
            if (ifr._deptBound) return;
            ifr._deptBound = true;
            if (ifr.contentDocument && ifr.contentDocument.readyState !== 'loading') {
              processIframe(ifr);
            } else {
              ifr.addEventListener('load', () => processIframe(ifr), { passive: true });
            }
          });
        }
      
        function applyNow(root = document) {
          injectStyleOnce(document);
          watchIframes();
          if (!currentDept) initialHideListedBlocks(root);
          else applyDepartmentVisibility(root);
        }
      
        function startObserver() {
          if (mainObserver) return;
          mainObserver = new MutationObserver(() => applyNow(document));
          mainObserver.observe(document.documentElement, OBS_CONFIG);
          ['#app','.rise-player','[data-rise]','.course-container'].forEach(sel => {
            const c = document.querySelector(sel);
            if (c && !c._deptObserver) {
              const o = new MutationObserver(() => applyNow(c));
              o.observe(c, OBS_CONFIG);
              c._deptObserver = o;
            }
          });
        }
      
        function resolveDepartmentAndApply() {
          const dept = getUserDepartment();
          if (!dept) {
            log('user_department не знайдено (поки що). Працюємо у режимі initial-hide.');
            return;
          }
          if (dept === currentDept) return;
          currentDept = dept;
          computeAllowedIdsFor(currentDept);
          applyNow(document);
        }
      
        function waitForDataThenInit() {
          if (initialized) return;
          initialized = true;
          injectStyleOnce(document);
          collectAllListedIds();
          initialHideListedBlocks(document);
          watchIframes();
          startObserver();
      
          let tries = 0;
          const quick = setInterval(() => {
            tries++;
            if (isUserDataReady()) {
              resolveDepartmentAndApply();
              if (currentDept) clearInterval(quick);
            }
            if (tries >= 20) clearInterval(quick);
          }, 500);
      
          setInterval(resolveDepartmentAndApply, 2000);
        }
      
        DB.forceApply = () => applyNow(document);
        DB.setDepartment = (deptName) => {
          DB.overrideDepartment = deptName;
          resolveDepartmentAndApply();
        };
      
        if (document.readyState === 'loading') {
          document.addEventListener('DOMContentLoaded', () => setTimeout(waitForDataThenInit, 50), { once: true });
        } else {
          setTimeout(waitForDataThenInit, 50);
        }
      })();
    </script>
    

    Як це працює:

    • на старті всі «керовані» тимчасово блоки приховані;
    • коли система зчитує user_department, вона показує лише ті блоки, які ви закріпили за цим департаментом;
    • блоки з інших списків залишаються прихованими, а ті, яких немає в mapping – видимі;
    • якщо департамент не знайдено або він порожній – користувач бачить default.

    В результаті кожен співробітник отримує тільки свій контент – без дублювання курсу та зайвих версій.

    blank

    Підсумуємо

    Тепер у вас є три готові інструменти:

    • Скрипт №1 – отримує дані користувача, підтягуючи назву підрозділу або персональне фото.
    • Скрипт №2 – змінює слова й закінчення в тексті залежно від гендеру користувача.
    • Скрипт №3 – керує контентом: приховує чи відображає блоки курсу для різних департаментів.

    Вам не обов’язково підключати всі одразу – використовуйте тільки ті, що справді потрібні для вашого курсу.

    blank

    Останній крок: пакування та завантаження курсу в LMS

    Коли всі зміни внесені, залишилося зробити найпростіше – правильно запакувати курс.

    Як це зробити:

    1. Виділіть усі файли та папки всередині проєкту (але не саму верхню папку).
    2. Запакуйте їх у форматі ZIP за допомогою будь-якого зручного архіватора.
    3. Завантажте отриманий ZIP файл в LMS Collaborator.

    blank

    Висновок

    В статті ми пройшли весь шлях: від того, як підтягувати дані з LMS і підставляти реальні фото, до зміни звернень за гендером і керування блоками курсу для різних департаментів.
    Тепер курс працює «з коробки» та перетворюється на багато версій без дублювання та зайвої роботи.

    Що ви отримуєте:

    • Персоналізація – звернення по імені, фото користувача, тексти під стать.
    • Контроль над контентом – показуєте різні блоки для різних департаментів.
    • Економія часу – менше ручної роботи, менше дублювання.
    • Зручність для команди – навчання стає зрозумілим, живим і близьким до потреб співробітників.

    Головна ідея проста: менше технічних маніпуляцій, більше персоналізації, гнучкості та цінності для всіх.😊

    У LMS Collaborator ми допомагаємо зробити навчання більш персональним і зручним для кожного співробітника. Платформа дає можливості налаштувати курси під різні ролі, департаменти та потреби команди. Якщо хочете створювати курси, які враховують особливості користувачів і роблять навчання ефективнішим, спробуйте LMS Collaborator.
    blank
    Atamanenko Katya
    Испытайте LMS Collaborator в действии
    Нужны рекомендации по выбору правильных функций для оцифровки и автоматизации корпоративных процессов обучения? Мы здесь, чтобы помочь.
    Заказать демо
    Или звоните нашему менеджеру
    +38(067)217-0440