feat: Use dropdown for units when more then 15

When dealing with subsections that have a lot of units, show a dropdown for
unit selection to simplify navigation.
This commit is contained in:
kshitij.sobti
2025-05-19 15:42:25 +05:30
committed by Farhaan Bukhsh
parent bbc0cc2baa
commit 0fbfc1cf54
3 changed files with 126 additions and 3 deletions

View File

@@ -4,6 +4,23 @@
from django.conf import settings
%>
<script type="text/template" id="dropdown-button-tpl">
<li id="dropdown-container" class="h-100">
<button
id="dropdown-sequence-list-button"
class="dropdown-toggle"
type="button"
>
<span class="icon fa fa-chevron-down" aria-hidden="true"></span>
</button>
<div id="dropdown-sequence-list" style="display: none; position: absolute; width: 240px; right: 0;">
<ol class="d-block dropdown-menu bg-white py-0 shadow-sm border"
aria-labelledby="dropdown-sequence-list-button">
</ol>
</div>
</li>
</script>
<div id="sequence_${element_id}" class="sequence" data-id="${item_id}"
data-position="${position}"
data-next-url="${next_url}" data-prev-url="${prev_url}"
@@ -46,7 +63,7 @@
</li>
% else:
% for idx, item in enumerate(items):
<li role="presentation">
<li role="presentation" class="sequence-list-item">
<button class="seq_${item['type']} inactive nav-item tab"
role="tab"
tabindex="-1"
@@ -98,6 +115,9 @@
</ul>
</li>
% endif
% if show_dropdown:
## <%include file='seq_dropdown.html' args="items=items[15:], start_index=15"/>
% endif
</ol>
</nav>
</div>

View File

@@ -4,6 +4,8 @@
(function() {
'use strict';
const { HtmlUtils } = window.edx;
this.Sequence = (function() {
function Sequence(element, runtime) {
var self = this;
@@ -26,6 +28,9 @@
this.goto = function(event) {
return Sequence.prototype.goto.apply(self, [event]);
};
this.toggleDropdown = function(event) {
return Sequence.prototype.toggleDropdown.apply(self, [event]);
};
this.toggleArrows = function() {
return Sequence.prototype.toggleArrows.apply(self);
};
@@ -38,6 +43,12 @@
this.displayTabTooltip = function(event) {
return Sequence.prototype.displayTabTooltip.apply(self, [event]);
};
this.renderDropdown = function() {
return Sequence.prototype.renderDropdown.apply(self);
}
this.handleClickOutsideDropdown = function(event) {
return Sequence.prototype.handleClickOutsideDropdown.apply(self, [event]);
}
this.arrowKeys = {
LEFT: 37,
UP: 38,
@@ -62,23 +73,87 @@
this.showCompletion = this.el.data('show-completion');
this.keydownHandler($(element).find('#sequence-list .tab'));
this.base_page_title = ($('title').data('base-title') || '').trim();
this.dropdownButtonTpl = _.template($('#dropdown-button-tpl').text())({});
this.renderDropdown();
this.bind();
this.render(parseInt(this.el.data('position'), 10));
}
Sequence.prototype.renderDropdown = function() {
// Renders the dropdown when there isn't enough space for all units in the bar
// Hide the dropdown by default and only show if needed.
this.$('#sequence-list > #dropdown-container').hide();
this.$(`#sequence-list > li.sequence-list-item`).show();
// Calculate the number of tabs that can fit comfortably and if the
// number of units is greater we show the dropdown.
const tabListWidth = this.$('#sequence-list').width();
const singleTabWidth = this.$('#sequence-list > li:first').width();
const tabCount = this.$('#sequence-list > li.sequence-list-item').length;
const overFlowCount = Math.floor(tabListWidth / singleTabWidth);
// Reduce 1 to offsets index and another one to accommodate the button
const overFlowIdx = overFlowCount - 2;
const showDropdown = overFlowCount < tabCount;
if (!showDropdown) {
return;
}
// If the dropdown button doesn't exist add it, otherwise move the
// existing button to the correct place.
if (this.$('#sequence-list > #dropdown-container').length === 0) {
// xss-lint: disable=javascript-jquery-insertion
this.$('#sequence-list > li.sequence-list-item').eq(overFlowIdx).after(this.dropdownButtonTpl);
} else {
this.$('#sequence-list > li.sequence-list-item').eq(overFlowIdx)
// xss-lint: disable=javascript-jquery-insertion
.after(this.$('#sequence-list > #dropdown-container'));
}
// Show the dropdown UX and hide all the overflowing unit buttons.
this.$('#sequence-list > #dropdown-container').show();
this.$(`#sequence-list > li.sequence-list-item:lt(${overFlowIdx + 1})`).show();
this.$(`#sequence-list > li.sequence-list-item:gt(${overFlowIdx})`).hide();
const dropdownList = this.$('#dropdown-sequence-list > ol');
// The dropdown buttons are modified copies of the unit nav buttons.
dropdownList.empty();
this.$(`#sequence-list > li.sequence-list-item:gt(${overFlowIdx})`).each((idx, el) => {
const cloneEl = $(el).clone();
const navButton = cloneEl.find("button");
const unitTitle = navButton.data('page-title');
navButton.click(self.goto);
navButton.find(".sequence-tooltip").remove();
navButton.find("span.icon").after(
HtmlUtils.joinHtml(HtmlUtils.HTML('<span class="unit-title">'), unitTitle, HtmlUtils.HTML('</span>')).toString()
);
//xss-lint: disable=javascript-jquery-insert-into-target
cloneEl.show().appendTo(dropdownList);
});
}
Sequence.prototype.$ = function(selector) {
return $(selector, this.el);
};
Sequence.prototype.bind = function() {
this.$('#sequence-list .nav-item').click(this.goto);
$(document).click(this.handleClickOutsideDropdown);
this.$('#dropdown-sequence-list .dropdown-item').click(this.goto);
this.$('#dropdown-sequence-list-button').click(this.toggleDropdown);
this.$('#sequence-list .nav-item').keypress(this.keyDownHandler);
this.el.on('bookmark:add', this.addBookmarkIconToActiveNavItem);
this.el.on('bookmark:remove', this.removeBookmarkIconFromActiveNavItem);
this.$('#sequence-list .nav-item').on('focus mouseenter', this.displayTabTooltip);
this.$('#sequence-list .nav-item').on('blur mouseleave', this.hideTabTooltip);
$(window).on('resize', _.debounce(this.renderDropdown.bind(this), 200));
};
Sequence.prototype.handleClickOutsideDropdown = function(event) {
if(!this.$('#dropdown-container')?.[0]?.contains(event.target)) {
this.$('#dropdown-sequence-list').hide();
}
}
Sequence.prototype.toggleDropdown = function() {
$('#dropdown-sequence-list').toggle();
}
Sequence.prototype.previousNav = function(focused, index) {
var $navItemList,
$sequenceList = $(focused).parent().parent();
@@ -289,6 +364,7 @@
Sequence.prototype.goto = function(event) {
var alertTemplate, alertText, isBottomNav, newPosition, widgetPlacement;
event.preventDefault();
this.$('#dropdown-sequence-list').hide();
// Links from courseware <a class='seqnav' href='n'>...</a>, was .target_tab
if ($(event.currentTarget).hasClass('seqnav')) {

View File

@@ -78,6 +78,13 @@
position: relative;
height: 100%;
flex-grow: 1;
max-width: calc(100% - 80px);
}
@media (min-width: 768px) {
.xmodule_display.xmodule_SequenceBlock .sequence-nav .sequence-list-wrapper {
max-width: calc(100% - 160px);
}
}
@media (max-width: 575.98px) {
@@ -91,7 +98,7 @@
display: flex;
}
.xmodule_display.xmodule_SequenceBlock .sequence-nav ol li {
.xmodule_display.xmodule_SequenceBlock .sequence-nav ol li, .dropdown-toggle {
box-sizing: border-box;
min-width: 40px;
flex-grow: 1;
@@ -104,7 +111,11 @@
border-right-style: solid;
}
.xmodule_display.xmodule_SequenceBlock .sequence-nav ol li button {
.xmodule_display.xmodule_SequenceBlock .sequence-nav ol li .dropdown-toggle {
height: 49px !important;
}
.xmodule_display.xmodule_SequenceBlock .sequence-nav ol li button, .dropdown-toggle {
width: 100%;
height: 49px;
position: relative;
@@ -119,6 +130,22 @@
overflow: visible;
}
.xmodule_display.xmodule_SequenceBlock .sequence-nav #dropdown-container ol li button {
display: flex;
align-items: center;
padding-left: 0.5rem;
padding-right: 0.5rem;
}
.xmodule_display.xmodule_SequenceBlock .sequence-nav #dropdown-container ol li button .unit-title {
display: flex;
flex-grow: 1;
text-overflow: ellipsis;
overflow: hidden;
white-space: nowrap;
margin: 0 0.5rem;
}
.xmodule_display.xmodule_SequenceBlock .sequence-nav ol li button .icon {
display: inline-block;
line-height: 100%;