loader image
Skip to main content
  JELA
  • Categories
    • All categories
    • Junior JELA
    • NextGen JELA
    • Open JELA
  • Home
Log in
Forgot your password?

Or login using your account

  JELA
Categories Collapse Expand
  • All categories
  • Junior JELA
  • NextGen JELA
  • Open JELA
Home

โ€ข ๐Ÿšงโœจ Our site is currently under preparation for Sekolah Rintis Bangsa Johor. Stay tuned.

    Johor English Language Academy

    Expand all

    Junior JELA

    NextGen JELA

    Open JELA

    Categories
    • All categories
    • Junior JELA
    • NextGen JELA
    • Open JELA
    0 Courses
    Newest

    Default

    Date

    Newest

    Oldest

    Alphabetical

    A to Z

    Z to A

    Show More Show Less
    Show:4 Row

    Show:2 Row

    Show:3 Row

    Show:4 Row

    Show:5 Row

    Show:6 Row

    Contact site support
    You are not logged in. (Log in)
    Data retention summary
    Get the mobile app
    (async () => { const LOG = '[Completion]'; const log = (...a) => console.log(LOG, ...a); const warn = (...a) => console.warn(LOG, ...a); const err = (...a) => console.error(LOG, ...a); // 1) New token (as requested) const token = "03978123ccb6ba97da164343c0224b41"; const domain = window.location.origin; // 3) Page-type whitelist (by body id prefix) // Examples: "page-mod-lti-view", "page-mod-scorm-view" const ALLOWED_PAGE_ID_PREFIXES = [ "page-mod-lti-", ]; const bodyId = String(document.body?.id || ""); const isAllowedPageType = ALLOWED_PAGE_ID_PREFIXES.some(prefix => bodyId.startsWith(prefix)); if (!isAllowedPageType) { warn("Not an allowed activity page type. Script will not run.", { bodyId, allowedPrefixes: ALLOWED_PAGE_ID_PREFIXES }); return; } // ---- ID RESOLUTION (using M.cfg + cmid method) const courseId = window.M?.cfg?.courseId ?? window.M?.cfg?.courseid ?? null; const userId = window.M?.cfg?.userid ?? window.M?.cfg?.userId ?? null; const cmid = Number(new URLSearchParams(location.search).get('cmid')) || Number(new URLSearchParams(location.search).get('id')) || null; console.log('M.cfg + cmid:', { courseId, userId, cmid, bodyId }); if (!courseId || !userId || !cmid) { warn('Missing IDs. Polling cannot start.', { courseId, userId, cmid }); return; } // ---- CONFIG const pollIntervalMs = 5000; const windowMinutes = 10; const windowMs = windowMinutes * 60 * 1000; const startedAt = Date.now(); let completed = false; let pollTimer = null; // ---- UI IDs const PANEL_ID = 'completionControlPanel'; const BTN_ID = 'completionManualRefresh'; const AUTO_ID = 'completionAutoStatus'; const SEP_ID = 'completionSeparator'; // ---- TIME const formatMMSS = (ms) => { const total = Math.max(0, Math.ceil(ms / 1000)); const m = String(Math.floor(total / 60)).padStart(2, '0'); const s = String(total % 60).padStart(2, '0'); return `${m}:${s}`; }; const remainingMs = () => Math.max(0, windowMs - (Date.now() - startedAt)); const updateAutoText = () => { const auto = document.getElementById(AUTO_ID); if (!auto) return; const timeEl = auto.querySelector('[data-role="time"]'); if (timeEl) timeEl.textContent = formatMMSS(remainingMs()); }; const hideAuto = () => { const auto = document.getElementById(AUTO_ID); const sep = document.getElementById(SEP_ID); if (auto) auto.style.display = 'none'; if (sep) sep.style.display = 'none'; }; const showAuto = () => { const auto = document.getElementById(AUTO_ID); const sep = document.getElementById(SEP_ID); if (auto) auto.style.display = 'inline-flex'; if (sep) sep.style.display = 'inline-flex'; }; // ---- UI refresh async function refreshActivityHeader() { try { const r = await fetch(window.location.href, { credentials: 'include' }); const html = await r.text(); const doc = new DOMParser().parseFromString(html, 'text/html'); const newHeader = doc.querySelector('.activity-header[data-for="page-activity-header"]'); const currentHeader = document.querySelector('.activity-header[data-for="page-activity-header"]'); if (newHeader && currentHeader) { currentHeader.replaceWith(newHeader); log('Activity header refreshed (DOM replaced).'); ensurePanel(); return true; } warn('Activity header not found to refresh.'); ensurePanel(); return false; } catch (e) { err('Header refresh failed:', e); ensurePanel(); return false; } } function refreshCourseNav() { try { require(['core_courseformat/courseeditor'], function(ce) { const editor = ce.getCurrentCourseEditor(); log('Dispatching courseformat refresh for cmid:', cmid); editor.dispatch('cmState', [cmid]); editor.dispatch('courseState'); }); } catch (e) { err('Course navigation refresh failed:', e); } } async function refreshAll(button = null) { try { ensurePanel(); if (button) { const icon = button.querySelector('i'); if (icon) icon.classList.add('fa-spin'); button.disabled = true; } log('Manual refresh triggered.'); await refreshActivityHeader(); refreshCourseNav(); } finally { const btn = document.getElementById(BTN_ID); if (btn) { const icon = btn.querySelector('i'); if (icon) icon.classList.remove('fa-spin'); btn.disabled = false; } ensurePanel(); } } // ---- Panel layout // [(icon) Refresh completion manually] โ€”โ€”โ€” (icon) Automatically checking completion (active โ€” 09:42 left) const buildPanel = () => { const panel = document.createElement('div'); panel.id = PANEL_ID; panel.style.cssText = ` margin-top: 10px; padding: 10px 0 0 0; display: flex; align-items: center; gap: 12px; flex-wrap: wrap; font-size: 0.92rem; `; const btn = document.createElement('button'); btn.id = BTN_ID; btn.type = 'button'; btn.className = 'btn btn-outline-primary'; btn.style.cssText = ` border-radius: 25px; display: inline-flex; align-items: center; gap: 6px; padding: 6px 16px; font-weight: 500; white-space: nowrap; `; btn.innerHTML = ` Refresh completion manually `; btn.addEventListener('click', () => refreshAll(btn)); const sep = document.createElement('span'); sep.id = SEP_ID; sep.textContent = 'โ€”โ€”โ€”'; sep.style.cssText = `color:#adb5bd; user-select:none;`; const auto = document.createElement('span'); auto.id = AUTO_ID; auto.style.cssText = ` color: #6c757d; display: inline-flex; align-items: center; gap: 6px; white-space: nowrap; `; auto.innerHTML = ` Automatically checking completion (active โ€” left) `; panel.appendChild(btn); panel.appendChild(sep); panel.appendChild(auto); return panel; }; const ensurePanel = () => { if (!document.getElementById(PANEL_ID)) { const panel = buildPanel(); // Prefer placing directly below the top completion block const completionInfo = document.querySelector('[data-region="completion-info"]'); if (completionInfo) { completionInfo.insertAdjacentElement('afterend', panel); log('Control panel inserted below top completion.'); } else { const activityInfo = document.querySelector('[data-region="activity-information"]'); if (activityInfo) { activityInfo.insertAdjacentElement('afterend', panel); warn('completion-info not found; panel inserted after activity-information.'); } else { document.body.prepend(panel); warn('completion-info & activity-information not found; panel inserted at top of body.'); } } } // Update timer display and visibility rules updateAutoText(); if (completed) hideAuto(); }; // Keep panel alive if DOM is replaced const observer = new MutationObserver(() => { if (!document.getElementById(PANEL_ID)) { log('Control panel missing after DOM mutation; restoring.'); ensurePanel(); } }); observer.observe(document.body, { childList: true, subtree: true }); // ---- Polling via REST token const stopPollingAndHideAuto = (reason) => { if (pollTimer) clearInterval(pollTimer); pollTimer = null; log(`Polling stopped (${reason}).`); ensurePanel(); hideAuto(); }; async function checkCompletion() { if (completed) return; ensurePanel(); updateAutoText(); if (remainingMs() <= 0) { stopPollingAndHideAuto(`time window ended (${windowMinutes} minutes)`); return; } const params = new URLSearchParams({ wstoken: token, wsfunction: "core_completion_get_activities_completion_status", moodlewsrestformat: "json", courseid: courseId, userid: userId }); try { const response = await fetch(`${domain}/webservice/rest/server.php?${params.toString()}`, { method: "GET" }); const data = await response.json(); if (data.exception) { err("Web service error:", data); return; } const activity = data.statuses?.find(s => Number(s.cmid) === Number(cmid)); log("Completion status:", activity); if (activity && Number(activity.state) > 0) { completed = true; log("Completion detected. Updating UI."); // Refresh UI once, then hide auto status await refreshActivityHeader(); refreshCourseNav(); ensurePanel(); hideAuto(); stopPollingAndHideAuto("completed"); } } catch (e) { err("Web service fetch failed:", e); } } // ---- Boot ensurePanel(); showAuto(); updateAutoText(); // Update the displayed timer every second (UI only) const uiTimer = setInterval(() => { if (completed || remainingMs() <= 0) { clearInterval(uiTimer); return; } ensurePanel(); updateAutoText(); }, 1000); log(`Auto checking enabled for this page type. Window: ${windowMinutes} minutes.`); await checkCompletion(); pollTimer = setInterval(checkCompletion, pollIntervalMs); })();
    Powered by Moodle









      JELA
    Privacy Policy Terms & Conditions

    Johor English Language Academy ยฉ 2026. All rights reserved.