Skip to content

Commit 7b868f1

Browse files
Merge pull request #7508 from christianbeeznest/GH-7469-3
Learnpath: Fix Chamilo LP next/prev navigation when TOC is hidden - refs #7469
2 parents ff60a7a + f2f5185 commit 7b868f1

2 files changed

Lines changed: 157 additions & 44 deletions

File tree

src/CoreBundle/Resources/views/LearnPath/menubar.html.twig

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,9 @@
4242
>
4343
<i class="mdi-home mdi" aria-hidden="true"></i>
4444
</a>
45-
{{ navigation_bar }}
45+
<span data-lp-menu="nav">
46+
{{ navigation_bar }}
47+
</span>
4648
</div>
4749
<button title="{{ 'Options'|trans }}" class="menu-button icons" type="button" data-lp-menu="toggle">
4850
<i class="mdi-menu mdi" aria-hidden="true"></i>

src/CoreBundle/Resources/views/LearnPath/view.html.twig

Lines changed: 154 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -414,7 +414,7 @@
414414
*/
415415
$menu.toggleClass("c-menu-top", tocHidden === 1);
416416
417-
// Prefer data attributes (if you added them), fallback to legacy classes.
417+
// Prefer data attributes, fallback to legacy classes.
418418
var $circle = $menu.find("[data-lp-menu='circle']").first();
419419
if (!$circle.length) {
420420
$circle = $menu.find(".circle").first();
@@ -529,13 +529,67 @@
529529
}
530530
}
531531
532+
/* -----------------------------
533+
* Iframe helpers
534+
* ----------------------------- */
535+
function getLpIframe() {
536+
var $i = $("#content_id");
537+
if (!$i.length) {
538+
$i = $("#content_id_blank");
539+
}
540+
return $i;
541+
}
542+
543+
function syncCurrentItemFromIframe() {
544+
try {
545+
var $i = getLpIframe();
546+
if (!$i.length) {
547+
return;
548+
}
549+
var src = $i.attr("src") || "";
550+
var m = src.match(/[?&]item_id=(\d+)/);
551+
if (m && m[1]) {
552+
window.lpCurrentItemId = parseInt(m[1], 10) || window.lpCurrentItemId;
553+
}
554+
} catch (e) {}
555+
}
556+
532557
/* -----------------------------------------------------------
533-
* When TOC is hidden, navigation must NOT rely on
534-
* TOC DOM ordering. Use backend/runtime directions ('next'/'previous')
535-
* to guarantee correct sequencing.
558+
* TOC-based navigation helpers
536559
* ----------------------------------------------------------- */
560+
function collectTocItemIds() {
561+
var ids = [];
562+
var seen = {};
563+
564+
$("#toc_id [id^='toc_']").each(function () {
565+
var raw = (this.id || "").replace("toc_", "");
566+
var n = parseInt(raw, 10);
567+
if (!Number.isFinite(n) || n <= 0) return;
568+
if (seen[n]) return;
569+
seen[n] = true;
570+
ids.push(n);
571+
});
572+
573+
if (!ids.length) {
574+
$("#toc_id a[href*='item_id=']").each(function () {
575+
try {
576+
var href = $(this).attr("href") || "";
577+
var m = href.match(/[?&]item_id=(\d+)/);
578+
if (!m) return;
579+
var n = parseInt(m[1], 10);
580+
if (!Number.isFinite(n) || n <= 0) return;
581+
if (seen[n]) return;
582+
seen[n] = true;
583+
ids.push(n);
584+
} catch (e) {}
585+
});
586+
}
587+
588+
return ids;
589+
}
590+
537591
function getCurrentItemIdSafe() {
538-
// Prefer runtime value (always the most reliable after switches)
592+
// Prefer runtime value (most reliable when available)
539593
try {
540594
if (window.olms && typeof olms.lms_item_id !== "undefined") {
541595
var n = parseInt(olms.lms_item_id, 10);
@@ -550,6 +604,26 @@
550604
return Number.isFinite(fromUrl) ? fromUrl : 0;
551605
}
552606
607+
function resolveDirectionToId(direction, currentId) {
608+
var ids = collectTocItemIds();
609+
if (!ids.length) {
610+
return 0;
611+
}
612+
613+
var idx = ids.indexOf(currentId);
614+
if (idx === -1) {
615+
return direction === "next" ? (ids[0] || 0) : 0;
616+
}
617+
618+
if (direction === "next") {
619+
return ids[idx + 1] || 0;
620+
}
621+
if (direction === "previous") {
622+
return ids[idx - 1] || 0;
623+
}
624+
return 0;
625+
}
626+
553627
function buildContentUrlForItem(itemId) {
554628
var cid = getSearchParam("cid") || "";
555629
var sid = getSearchParam("sid") || "0";
@@ -565,10 +639,7 @@
565639
566640
// Iframe loading overlay (safe: does not cancel navigation)
567641
var $loader = $("#lp-iframe-loader");
568-
var $iframe = $("#content_id");
569-
if (!$iframe.length) {
570-
$iframe = $("#content_id_blank");
571-
}
642+
var $iframe = getLpIframe();
572643
573644
var loaderTimer = null;
574645
var maxHideTimer = null;
@@ -595,6 +666,7 @@
595666
if ($iframe.length) {
596667
showLoader("initial");
597668
$iframe.off("load.lpLoader").on("load.lpLoader", function () {
669+
syncCurrentItemFromIframe();
598670
hideLoader("iframe load");
599671
});
600672
} else {
@@ -643,62 +715,61 @@
643715
var isDirection = (target === "next" || target === "previous" || target === "first" || target === "last");
644716
var targetItemId = 0;
645717
718+
showLoader("navigate " + label);
719+
var currentItemId = getCurrentItemIdSafe();
720+
721+
// If target is numeric, validate it.
646722
if (!isDirection) {
647723
targetItemId = parseInt(target, 10);
648724
if (!Number.isFinite(targetItemId) || targetItemId <= 0) {
649725
console.warn("LP NAV: invalid target item id for " + label + ":", target);
726+
hideLoader("nav failed");
650727
return false;
651728
}
652729
}
653730
654-
showLoader("navigate " + label);
655-
656-
var currentItemId = getCurrentItemIdSafe();
731+
// If direction, first resolve it from TOC order (best for Chamilo LP).
732+
if (isDirection && (target === "next" || target === "previous")) {
733+
var tocResolved = resolveDirectionToId(target, currentItemId);
734+
if (tocResolved > 0 && tocResolved !== currentItemId) {
735+
targetItemId = tocResolved;
736+
}
737+
}
657738
658-
// Use switch_item if the runtime is ready (preferred, preserves SCORM state)
739+
// Prefer switch_item if runtime is ready (keeps SCORM/LP state).
659740
if (window.olms && typeof switch_item === "function" && Number(olms.lms_initialized) === 1) {
660741
console.info("LP NAV: switching item via runtime", {
661742
currentItemId: currentItemId,
662-
target: isDirection ? target : targetItemId
743+
target: isDirection ? (targetItemId > 0 ? targetItemId : target) : targetItemId
663744
});
664745
665746
try {
666-
// NOTE: when target is 'next'/'previous', the backend computes the real next/prev.
667-
switch_item(currentItemId || 0, isDirection ? target : targetItemId);
668-
669-
// Keep window var in sync only when we know the numeric target.
670-
if (!isDirection) {
747+
// If we resolved a numeric target, pass numeric. Otherwise pass direction.
748+
switch_item(currentItemId || 0, isDirection ? (targetItemId > 0 ? targetItemId : target) : targetItemId);
749+
if (!isDirection || targetItemId > 0) {
671750
window.lpCurrentItemId = targetItemId;
672751
}
673752
return true;
674753
} catch (e) {
675-
console.warn("LP NAV: switch_item() failed, fallback to iframe src update.", e);
754+
console.warn("LP NAV: switch_item() failed, falling back to iframe src update.", e);
676755
}
677756
}
678757
679-
// update iframe src (works, but may not update full LMS runtime state)
680-
if ($iframe && $iframe.length) {
681-
// If direction was requested, try to map it to a numeric id from runtime
682-
if (isDirection && window.olms) {
683-
var fallbackId = 0;
684-
try {
685-
if (target === "next") fallbackId = parseInt(olms.lms_next_item, 10);
686-
if (target === "previous") fallbackId = parseInt(olms.lms_previous_item, 10);
687-
} catch (e) {}
688-
689-
if (Number.isFinite(fallbackId) && fallbackId > 0 && fallbackId !== currentItemId) {
690-
targetItemId = fallbackId;
691-
} else {
692-
console.warn("LP NAV: cannot resolve numeric item for direction fallback:", target, fallbackId);
693-
hideLoader("nav failed");
694-
return false;
695-
}
696-
}
758+
// Fallback: resolve direction from runtime vars only if TOC resolution failed.
759+
if (isDirection && targetItemId <= 0 && window.olms) {
760+
try {
761+
if (target === "next") targetItemId = parseInt(olms.lms_next_item, 10) || 0;
762+
if (target === "previous") targetItemId = parseInt(olms.lms_previous_item, 10) || 0;
763+
} catch (e) {}
764+
}
697765
698-
if (targetItemId > 0) {
766+
// Final fallback: update iframe src directly.
767+
if (targetItemId > 0) {
768+
var $i = getLpIframe();
769+
if ($i.length) {
699770
var src = buildContentUrlForItem(targetItemId);
700771
console.info("LP NAV: updating iframe src (fallback).", src);
701-
$iframe.attr("src", src);
772+
$i.attr("src", src);
702773
window.lpCurrentItemId = targetItemId;
703774
return true;
704775
}
@@ -708,6 +779,44 @@
708779
return false;
709780
}
710781
782+
/* -----------------------------------------------------------
783+
* Deterministic bindings for Next/Previous (Chamilo LP safe)
784+
* ----------------------------------------------------------- */
785+
function bindPrimaryNavButtons() {
786+
var $prev = $("#scorm-previous");
787+
var $next = $("#scorm-next");
788+
789+
function bind($el, direction, label) {
790+
if (!$el || !$el.length) return;
791+
792+
// Tag for easier debugging
793+
$el.attr("data-lp-nav", direction);
794+
795+
// Remove legacy inline handlers to prevent NaN or dead navigation.
796+
// We still keep behavior via lpNavigateToItem().
797+
$el.removeAttr("onclick").removeAttr("target");
798+
799+
if ($el.is("a")) {
800+
$el.attr("href", "#");
801+
}
802+
803+
$el.off("click.lpNavPrimary").on("click.lpNavPrimary", function (e) {
804+
e.preventDefault();
805+
e.stopPropagation();
806+
console.info("LP NAV: " + label + " clicked -> navigating", direction);
807+
lpNavigateToItem(direction, label);
808+
return false;
809+
});
810+
}
811+
812+
if (!$prev.length || !$next.length) {
813+
console.warn("LP NAV: primary #scorm-previous/#scorm-next elements not found. Will rely on heuristic patching.");
814+
}
815+
816+
bind($prev, "previous", "previous");
817+
bind($next, "next", "next");
818+
}
819+
711820
function looksLikePrev(el) {
712821
var $el = $(el);
713822
var t = ($el.attr("title") || "").toLowerCase();
@@ -742,7 +851,7 @@
742851
function patchNavArrowsWhenTocHidden() {
743852
if (tocHidden !== 1) return;
744853
745-
// Prefer known IDs if present (most reliable on top menu)
854+
// Prefer known IDs if present (most reliable)
746855
var $prevEl = $("#scorm-previous");
747856
var $nextEl = $("#scorm-next");
748857
@@ -769,9 +878,7 @@
769878
return;
770879
}
771880
772-
// Remove legacy inline handlers and targets
773881
$el.removeAttr("onclick").removeAttr("target");
774-
775882
if ($el.is("a")) {
776883
$el.attr("href", "#");
777884
}
@@ -847,6 +954,10 @@
847954
}
848955
}
849956
957+
// Bind primary next/prev always (safe for Chamilo LP + SCORM)
958+
bindPrimaryNavButtons();
959+
setTimeout(bindPrimaryNavButtons, 200);
960+
setTimeout(bindPrimaryNavButtons, 600);
850961
if (tocHidden === 1) {
851962
// Run immediately + retries (menu markup sometimes arrives late)
852963
patchNavArrowsWhenTocHiddenSafe();

0 commit comments

Comments
 (0)