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

Empowering education with interactive English resources, training, and courses.

Begin Your Journey!

Learn. Grow. Excel.

About Us

At JELA, we are dedicated to improving English proficiency among primary and secondary students through engaging, interactive, and expertly curated resources.

icon-1

Mission

To empower learners and educators with tools, strategies, and resources that foster English excellence and inspire lifelong learning.

icon-2

Team

Our passionate team of Johor English teachers combines expertise, creativity, and commitment to provide a supportive and dynamic learning experience for all.

icon-3

Curriculum

JELA’s learning content aligns with the national educational standards, offering comprehensive lessons in reading, writing, listening, speaking, vocabulary and grammar to ensure holistic language development.

icon-4

Approach

We combine innovative teaching methods with interactive technology to create a learner-centric platform that engages, motivates, and supports all users effectively.

Discover and Enroll in Our Exciting Courses!

Junior JELA
Junior JELA builds strong English foundations for primary learners through fun and engaging activities.

JUNIOR JELA

NextGen JELA
NextGen JELA supports secondary learners in developing advanced English skills, critical thinking, and exam readiness.

NEXTGEN JELA

Open JELA
Open JELA offers flexible English learning resources for all ages, educators, and lifelong learners.

OPEN JELA

Johor English Language Academy


logo

Johor English Language Academy

Backed by experienced Johor English educators, we deliver innovative, interactive learning experiences to help you succeed.

Office

Sektor Pembelajaran,

Jabatan Pendidikan Negeri Johor,
Jalan Hang Jebat 1, Taman Skudai Baru, 81300 Skudai, Johor

+607-558 1893

Links

HomeCoursesHelpdesk

Newsletter

fbinsta

© 2026 Johor English Language Academy. All Rights Reserved

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.