diff --git a/lms/templates/seq_block.html b/lms/templates/seq_block.html
index 3c8699bc8f..9c7c900e50 100644
--- a/lms/templates/seq_block.html
+++ b/lms/templates/seq_block.html
@@ -4,6 +4,23 @@
from django.conf import settings
%>
+
+
% else:
% for idx, item in enumerate(items):
-
+
% endif
+ % if show_dropdown:
+ ## <%include file='seq_dropdown.html' args="items=items[15:], start_index=15"/>
+ % endif
diff --git a/xmodule/js/src/sequence/display.js b/xmodule/js/src/sequence/display.js
index 0c136a5b58..c8a692fbbf 100644
--- a/xmodule/js/src/sequence/display.js
+++ b/xmodule/js/src/sequence/display.js
@@ -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(''), unitTitle, HtmlUtils.HTML('')).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 ..., was .target_tab
if ($(event.currentTarget).hasClass('seqnav')) {
diff --git a/xmodule/static/css-builtin-blocks/SequenceBlockDisplay.css b/xmodule/static/css-builtin-blocks/SequenceBlockDisplay.css
index 53671879f6..cbc5b19f79 100644
--- a/xmodule/static/css-builtin-blocks/SequenceBlockDisplay.css
+++ b/xmodule/static/css-builtin-blocks/SequenceBlockDisplay.css
@@ -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%;