Skip to content

Commit ec79885

Browse files
xitij2000blarghmatey
authored andcommitted
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. (cherry picked from commit aaca11f)
1 parent 2a30d30 commit ec79885

3 files changed

Lines changed: 126 additions & 3 deletions

File tree

lms/templates/seq_block.html

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,23 @@
44
from django.conf import settings
55
%>
66

7+
<script type="text/template" id="dropdown-button-tpl">
8+
<li id="dropdown-container" class="h-100">
9+
<button
10+
id="dropdown-sequence-list-button"
11+
class="dropdown-toggle"
12+
type="button"
13+
>
14+
<span class="icon fa fa-chevron-down" aria-hidden="true"></span>
15+
</button>
16+
<div id="dropdown-sequence-list" style="display: none; position: absolute; width: 240px; right: 0;">
17+
<ol class="d-block dropdown-menu bg-white py-0 shadow-sm border"
18+
aria-labelledby="dropdown-sequence-list-button">
19+
</ol>
20+
</div>
21+
</li>
22+
</script>
23+
724
<div id="sequence_${element_id}" class="sequence" data-id="${item_id}"
825
data-position="${position}"
926
data-next-url="${next_url}" data-prev-url="${prev_url}"
@@ -46,7 +63,7 @@
4663
</li>
4764
% else:
4865
% for idx, item in enumerate(items):
49-
<li role="presentation">
66+
<li role="presentation" class="sequence-list-item">
5067
<button class="seq_${item['type']} inactive nav-item tab"
5168
role="tab"
5269
tabindex="-1"
@@ -98,6 +115,9 @@
98115
</ul>
99116
</li>
100117
% endif
118+
% if show_dropdown:
119+
## <%include file='seq_dropdown.html' args="items=items[15:], start_index=15"/>
120+
% endif
101121
</ol>
102122
</nav>
103123
</div>

xmodule/js/src/sequence/display.js

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44
(function() {
55
'use strict';
66

7+
const { HtmlUtils } = window.edx;
8+
79
this.Sequence = (function() {
810
function Sequence(element, runtime) {
911
var self = this;
@@ -26,6 +28,9 @@
2628
this.goto = function(event) {
2729
return Sequence.prototype.goto.apply(self, [event]);
2830
};
31+
this.toggleDropdown = function(event) {
32+
return Sequence.prototype.toggleDropdown.apply(self, [event]);
33+
};
2934
this.toggleArrows = function() {
3035
return Sequence.prototype.toggleArrows.apply(self);
3136
};
@@ -38,6 +43,12 @@
3843
this.displayTabTooltip = function(event) {
3944
return Sequence.prototype.displayTabTooltip.apply(self, [event]);
4045
};
46+
this.renderDropdown = function() {
47+
return Sequence.prototype.renderDropdown.apply(self);
48+
}
49+
this.handleClickOutsideDropdown = function(event) {
50+
return Sequence.prototype.handleClickOutsideDropdown.apply(self, [event]);
51+
}
4152
this.arrowKeys = {
4253
LEFT: 37,
4354
UP: 38,
@@ -62,23 +73,87 @@
6273
this.showCompletion = this.el.data('show-completion');
6374
this.keydownHandler($(element).find('#sequence-list .tab'));
6475
this.base_page_title = ($('title').data('base-title') || '').trim();
76+
this.dropdownButtonTpl = _.template($('#dropdown-button-tpl').text())({});
77+
this.renderDropdown();
6578
this.bind();
6679
this.render(parseInt(this.el.data('position'), 10));
6780
}
6881

82+
Sequence.prototype.renderDropdown = function() {
83+
// Renders the dropdown when there isn't enough space for all units in the bar
84+
// Hide the dropdown by default and only show if needed.
85+
this.$('#sequence-list > #dropdown-container').hide();
86+
this.$(`#sequence-list > li.sequence-list-item`).show();
87+
// Calculate the number of tabs that can fit comfortably and if the
88+
// number of units is greater we show the dropdown.
89+
const tabListWidth = this.$('#sequence-list').width();
90+
const singleTabWidth = this.$('#sequence-list > li:first').width();
91+
const tabCount = this.$('#sequence-list > li.sequence-list-item').length;
92+
const overFlowCount = Math.floor(tabListWidth / singleTabWidth);
93+
// Reduce 1 to offsets index and another one to accommodate the button
94+
const overFlowIdx = overFlowCount - 2;
95+
const showDropdown = overFlowCount < tabCount;
96+
if (!showDropdown) {
97+
return;
98+
}
99+
// If the dropdown button doesn't exist add it, otherwise move the
100+
// existing button to the correct place.
101+
if (this.$('#sequence-list > #dropdown-container').length === 0) {
102+
// xss-lint: disable=javascript-jquery-insertion
103+
this.$('#sequence-list > li.sequence-list-item').eq(overFlowIdx).after(this.dropdownButtonTpl);
104+
} else {
105+
this.$('#sequence-list > li.sequence-list-item').eq(overFlowIdx)
106+
// xss-lint: disable=javascript-jquery-insertion
107+
.after(this.$('#sequence-list > #dropdown-container'));
108+
}
109+
// Show the dropdown UX and hide all the overflowing unit buttons.
110+
this.$('#sequence-list > #dropdown-container').show();
111+
this.$(`#sequence-list > li.sequence-list-item:lt(${overFlowIdx + 1})`).show();
112+
this.$(`#sequence-list > li.sequence-list-item:gt(${overFlowIdx})`).hide();
113+
const dropdownList = this.$('#dropdown-sequence-list > ol');
114+
// The dropdown buttons are modified copies of the unit nav buttons.
115+
dropdownList.empty();
116+
this.$(`#sequence-list > li.sequence-list-item:gt(${overFlowIdx})`).each((idx, el) => {
117+
const cloneEl = $(el).clone();
118+
const navButton = cloneEl.find("button");
119+
const unitTitle = navButton.data('page-title');
120+
navButton.click(self.goto);
121+
navButton.find(".sequence-tooltip").remove();
122+
navButton.find("span.icon").after(
123+
HtmlUtils.joinHtml(HtmlUtils.HTML('<span class="unit-title">'), unitTitle, HtmlUtils.HTML('</span>')).toString()
124+
);
125+
//xss-lint: disable=javascript-jquery-insert-into-target
126+
cloneEl.show().appendTo(dropdownList);
127+
});
128+
}
129+
69130
Sequence.prototype.$ = function(selector) {
70131
return $(selector, this.el);
71132
};
72133

73134
Sequence.prototype.bind = function() {
74135
this.$('#sequence-list .nav-item').click(this.goto);
136+
$(document).click(this.handleClickOutsideDropdown);
137+
this.$('#dropdown-sequence-list .dropdown-item').click(this.goto);
138+
this.$('#dropdown-sequence-list-button').click(this.toggleDropdown);
75139
this.$('#sequence-list .nav-item').keypress(this.keyDownHandler);
76140
this.el.on('bookmark:add', this.addBookmarkIconToActiveNavItem);
77141
this.el.on('bookmark:remove', this.removeBookmarkIconFromActiveNavItem);
78142
this.$('#sequence-list .nav-item').on('focus mouseenter', this.displayTabTooltip);
79143
this.$('#sequence-list .nav-item').on('blur mouseleave', this.hideTabTooltip);
144+
$(window).on('resize', _.debounce(this.renderDropdown.bind(this), 200));
80145
};
81146

147+
Sequence.prototype.handleClickOutsideDropdown = function(event) {
148+
if(!this.$('#dropdown-container')?.[0]?.contains(event.target)) {
149+
this.$('#dropdown-sequence-list').hide();
150+
}
151+
}
152+
153+
Sequence.prototype.toggleDropdown = function() {
154+
$('#dropdown-sequence-list').toggle();
155+
}
156+
82157
Sequence.prototype.previousNav = function(focused, index) {
83158
var $navItemList,
84159
$sequenceList = $(focused).parent().parent();
@@ -289,6 +364,7 @@
289364
Sequence.prototype.goto = function(event) {
290365
var alertTemplate, alertText, isBottomNav, newPosition, widgetPlacement;
291366
event.preventDefault();
367+
this.$('#dropdown-sequence-list').hide();
292368

293369
// Links from courseware <a class='seqnav' href='n'>...</a>, was .target_tab
294370
if ($(event.currentTarget).hasClass('seqnav')) {

xmodule/static/css-builtin-blocks/SequenceBlockDisplay.css

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,13 @@
7878
position: relative;
7979
height: 100%;
8080
flex-grow: 1;
81+
max-width: calc(100% - 80px);
82+
}
83+
84+
@media (min-width: 768px) {
85+
.xmodule_display.xmodule_SequenceBlock .sequence-nav .sequence-list-wrapper {
86+
max-width: calc(100% - 160px);
87+
}
8188
}
8289

8390
@media (max-width: 575.98px) {
@@ -91,7 +98,7 @@
9198
display: flex;
9299
}
93100

94-
.xmodule_display.xmodule_SequenceBlock .sequence-nav ol li {
101+
.xmodule_display.xmodule_SequenceBlock .sequence-nav ol li, .dropdown-toggle {
95102
box-sizing: border-box;
96103
min-width: 40px;
97104
flex-grow: 1;
@@ -104,7 +111,11 @@
104111
border-right-style: solid;
105112
}
106113

107-
.xmodule_display.xmodule_SequenceBlock .sequence-nav ol li button {
114+
.xmodule_display.xmodule_SequenceBlock .sequence-nav ol li .dropdown-toggle {
115+
height: 49px !important;
116+
}
117+
118+
.xmodule_display.xmodule_SequenceBlock .sequence-nav ol li button, .dropdown-toggle {
108119
width: 100%;
109120
height: 49px;
110121
position: relative;
@@ -119,6 +130,22 @@
119130
overflow: visible;
120131
}
121132

133+
.xmodule_display.xmodule_SequenceBlock .sequence-nav #dropdown-container ol li button {
134+
display: flex;
135+
align-items: center;
136+
padding-left: 0.5rem;
137+
padding-right: 0.5rem;
138+
}
139+
140+
.xmodule_display.xmodule_SequenceBlock .sequence-nav #dropdown-container ol li button .unit-title {
141+
display: flex;
142+
flex-grow: 1;
143+
text-overflow: ellipsis;
144+
overflow: hidden;
145+
white-space: nowrap;
146+
margin: 0 0.5rem;
147+
}
148+
122149
.xmodule_display.xmodule_SequenceBlock .sequence-nav ol li button .icon {
123150
display: inline-block;
124151
line-height: 100%;

0 commit comments

Comments
 (0)