Skip to content

Commit 8ab3ebe

Browse files
committed
updatemenus: refactor where scrollbox is created
* Previously, the scrollbox was created every time the dropmenu was unfolded. * Now, the scrollbox is created right after creating the dropdown container. * Moved the logic to fold and unfold the dropdown menu to functions foldDropdownMenu and unfoldDropdownMenu. * Moved all the logic to compute the position and size of the scrollbox to function unfoldDropdownMenu. * Moved logic to disable the scrollbox to function foldDropdownMenu. * This refactor will help introduce a background <rect> element in the dropdown container before any dropdown buttons. * It will also help introduce a transition to show and hide the scrollbars.
1 parent 13508da commit 8ab3ebe

File tree

2 files changed

+182
-145
lines changed

2 files changed

+182
-145
lines changed

src/components/updatemenus/draw.js

Lines changed: 128 additions & 101 deletions
Original file line numberDiff line numberDiff line change
@@ -84,50 +84,47 @@ module.exports = function draw(gd) {
8484
.classed(constants.dropdownButtonGroupClassName, true)
8585
.style('pointer-events', 'all');
8686

87-
// whenever we add new menu, attach 'state' variable to node
88-
// to keep track of the active menu ('-1' means no menu is active)
89-
// and remove all dropped buttons (if any)
90-
if(headerGroups.enter().size()) {
91-
gButton
92-
.call(removeAllButtons)
93-
.attr(constants.menuIndexAttrName, '-1');
87+
// find dimensions before plotting anything (this mutates menuOpts)
88+
for(var i = 0; i < menuData.length; i++) {
89+
var menuOpts = menuData[i];
90+
findDimensions(gd, menuOpts);
9491
}
9592

93+
// setup scrollbox
94+
var scrollBoxId = 'updatemenus' + fullLayout._uid,
95+
scrollBox = new ScrollBox(gd, gButton, scrollBoxId);
96+
9697
// remove exiting header, remove dropped buttons and reset margins
98+
if(headerGroups.enter().size()) {
99+
foldDropdownMenu(gButton, scrollBox);
100+
}
101+
97102
headerGroups.exit().each(function(menuOpts) {
98103
d3.select(this).remove();
99104

100-
gButton
101-
.call(removeAllButtons)
102-
.attr(constants.menuIndexAttrName, '-1');
105+
foldDropdownMenu(gButton, scrollBox);
103106

104107
Plots.autoMargin(gd, constants.autoMarginIdRoot + menuOpts._index);
105108
});
106109

107-
// find dimensions before plotting anything (this mutates menuOpts)
108-
for(var i = 0; i < menuData.length; i++) {
109-
var menuOpts = menuData[i];
110-
findDimensions(gd, menuOpts);
111-
}
112-
113110
// draw headers!
114111
headerGroups.each(function(menuOpts) {
115112
var gHeader = d3.select(this);
116113

117114
var _gButton = menuOpts.type === 'dropdown' ? gButton : null;
118115
Plots.manageCommandObserver(gd, menuOpts, menuOpts.buttons, function(data) {
119-
setActive(gd, menuOpts, menuOpts.buttons[data.index], gHeader, _gButton, data.index, true);
116+
setActive(gd, menuOpts, menuOpts.buttons[data.index], gHeader, _gButton, scrollBox, data.index, true);
120117
});
121118

122119
if(menuOpts.type === 'dropdown') {
123-
drawHeader(gd, gHeader, gButton, menuOpts);
120+
drawHeader(gd, gHeader, gButton, scrollBox, menuOpts);
124121

125-
// update buttons if they are dropped
126-
if(areMenuButtonsDropped(gButton, menuOpts)) {
127-
drawButtons(gd, gHeader, gButton, menuOpts);
122+
// update dropdown buttons if this menu is active
123+
if(isActive(gButton, menuOpts)) {
124+
unfoldDropdownMenu(gd, gHeader, gButton, scrollBox, menuOpts);
128125
}
129126
} else {
130-
drawButtons(gd, gHeader, null, menuOpts);
127+
drawButtons(gd, gHeader, null, scrollBox, menuOpts);
131128
}
132129

133130
});
@@ -152,18 +149,104 @@ function makeMenuData(fullLayout) {
152149

153150
// Note that '_index' is set at the default step,
154151
// it corresponds to the menu index in the user layout update menu container.
155-
// This is a more 'consistent' field than e.g. the index in the menuData.
156-
function keyFunction(opts) {
157-
return opts._index;
152+
// Because a menu can b set invisible,
153+
// this is a more 'consistent' field than the index in the menuData.
154+
function keyFunction(menuOpts) {
155+
return menuOpts._index;
156+
}
157+
158+
function isFolded(gButton) {
159+
return +gButton.attr(constants.menuIndexAttrName) === -1;
160+
}
161+
162+
function isActive(gButton, menuOpts) {
163+
return +gButton.attr(constants.menuIndexAttrName) === menuOpts._index;
164+
}
165+
166+
function unfoldDropdownMenu(gd, gHeader, gButton, scrollBox, menuOpts) {
167+
// enable the scrollbox
168+
var direction = menuOpts.direction,
169+
isUp = (direction === 'up'),
170+
isDown = (direction === 'down'),
171+
isLeft = (direction === 'left'),
172+
isRight = (direction === 'right'),
173+
isVertical = (isUp || isDown);
174+
175+
var x0, y0;
176+
if(isDown) {
177+
x0 = 0;
178+
y0 = menuOpts.headerHeight + constants.gapButtonHeader;
179+
}
180+
else if(isUp) {
181+
x0 = 0;
182+
y0 = menuOpts.headerHeight + constants.gapButton - menuOpts.openHeight;
183+
}
184+
else if(isRight) {
185+
x0 = menuOpts.headerWidth + constants.gapButtonHeader;
186+
y0 = 0;
187+
}
188+
else if(isLeft) {
189+
x0 = menuOpts.headerWidth + constants.gapButton - menuOpts.openWidth;
190+
y0 = 0;
191+
}
192+
193+
var position = {
194+
l: menuOpts.lx + menuOpts.borderwidth + x0 + menuOpts.pad.l,
195+
t: menuOpts.ly + menuOpts.borderwidth + y0 + menuOpts.pad.t,
196+
w: Math.max(menuOpts.openWidth, menuOpts.headerWidth),
197+
h: menuOpts.openHeight
198+
};
199+
200+
var active = menuOpts.active,
201+
translateX, translateY,
202+
i;
203+
if(isVertical) {
204+
translateY = 0;
205+
for(i = 0; i < active; i++) {
206+
translateY += menuOpts.heights[i] + constants.gapButton;
207+
}
208+
}
209+
else {
210+
translateX = 0;
211+
for(i = 0; i < active; i++) {
212+
translateX += menuOpts.widths[i] + constants.gapButton;
213+
}
214+
}
215+
216+
scrollBox.enable(position, translateX, translateY);
217+
218+
// store index of active menu (-1 means dropdown menu is folded)
219+
gButton.attr(constants.menuIndexAttrName, menuOpts._index);
220+
221+
drawButtons(gd, gHeader, gButton, scrollBox, menuOpts);
158222
}
159223

160-
function areMenuButtonsDropped(gButton, menuOpts) {
161-
var droppedIndex = +gButton.attr(constants.menuIndexAttrName);
224+
function foldDropdownMenu(gButton, scrollBox) {
225+
scrollBox.disable();
162226

163-
return droppedIndex === menuOpts._index;
227+
// -1 means dropdown menu is folded
228+
gButton
229+
.attr(constants.menuIndexAttrName, '-1')
230+
.call(removeAllButtons);
164231
}
165232

166-
function drawHeader(gd, gHeader, gButton, menuOpts) {
233+
function setActive(gd, menuOpts, buttonOpts, gHeader, gButton, scrollBox, buttonIndex, isSilentUpdate) {
234+
// update 'active' attribute in menuOpts
235+
menuOpts._input.active = menuOpts.active = buttonIndex;
236+
237+
if(menuOpts.type === 'dropdown') {
238+
// fold up buttons and redraw header
239+
gButton.attr(constants.menuIndexAttrName, '-1');
240+
241+
drawHeader(gd, gHeader, gButton, scrollBox, menuOpts);
242+
}
243+
244+
if(!isSilentUpdate || menuOpts.type === 'buttons') {
245+
drawButtons(gd, gHeader, gButton, scrollBox, menuOpts);
246+
}
247+
}
248+
249+
function drawHeader(gd, gHeader, gButton, scrollBox, menuOpts) {
167250
var header = gHeader.selectAll('g.' + constants.headerClassName)
168251
.data([0]);
169252

@@ -200,16 +283,18 @@ function drawHeader(gd, gHeader, gButton, menuOpts) {
200283
});
201284

202285
header.on('click', function() {
203-
gButton.call(removeAllButtons);
204-
205-
// if clicked index is same as dropped index => fold
206-
// otherwise => drop buttons associated with header
207-
gButton.attr(
208-
constants.menuIndexAttrName,
209-
areMenuButtonsDropped(gButton, menuOpts) ? '-1' : String(menuOpts._index)
210-
);
211-
212-
drawButtons(gd, gHeader, gButton, menuOpts);
286+
if(isFolded(gButton)) {
287+
unfoldDropdownMenu(gd, gHeader, gButton, scrollBox, menuOpts);
288+
}
289+
else if(isActive(gButton, menuOpts)) {
290+
foldDropdownMenu(gButton, scrollBox);
291+
}
292+
else {
293+
// the dropdown menu is unfolded,
294+
// but the clicked header is not the active header
295+
foldDropdownMenu(gButton, scrollBox);
296+
unfoldDropdownMenu(gd, gHeader, gButton, scrollBox, menuOpts);
297+
}
213298
});
214299

215300
header.on('mouseover', function() {
@@ -224,7 +309,7 @@ function drawHeader(gd, gHeader, gButton, menuOpts) {
224309
Lib.setTranslate(gHeader, menuOpts.lx, menuOpts.ly);
225310
}
226311

227-
function drawButtons(gd, gHeader, gButton, menuOpts) {
312+
function drawButtons(gd, gHeader, gButton, scrollBox, menuOpts) {
228313
// If this is a set of buttons, set pointer events = all since we play
229314
// some minor games with which container is which in order to simplify
230315
// the drawing of *either* buttons or menus
@@ -233,7 +318,7 @@ function drawButtons(gd, gHeader, gButton, menuOpts) {
233318
gButton.attr('pointer-events', 'all');
234319
}
235320

236-
var buttonData = (gButton.attr(constants.menuIndexAttrName) !== '-1' || menuOpts.type === 'buttons') ?
321+
var buttonData = (!isFolded(gButton) || menuOpts.type === 'buttons') ?
237322
menuOpts.buttons :
238323
[];
239324

@@ -258,7 +343,7 @@ function drawButtons(gd, gHeader, gButton, menuOpts) {
258343
.each('end', function() {
259344
// remove the scrollbox, if all the buttons have been removed
260345
if(gButton.selectAll('g.' + klass).size() === 0) {
261-
gButton.call(removeAllButtons);
346+
foldDropdownMenu(gButton, scrollBox);
262347
}
263348
});
264349
} else {
@@ -297,16 +382,6 @@ function drawButtons(gd, gHeader, gButton, menuOpts) {
297382
index: 0,
298383
};
299384

300-
var fullLayout = gd._fullLayout,
301-
scrollBoxId = 'updatemenus' + fullLayout._uid + menuOpts._index,
302-
scrollBoxPosition = {
303-
l: menuOpts.lx + menuOpts.borderwidth + x0 + menuOpts.pad.l,
304-
t: menuOpts.ly + menuOpts.borderwidth + y0 + menuOpts.pad.t,
305-
w: Math.max(menuOpts.openWidth, menuOpts.headerWidth),
306-
h: menuOpts.openHeight
307-
},
308-
scrollBox = new ScrollBox(gd, gButton, scrollBoxPosition, scrollBoxId);
309-
310385
buttons.each(function(buttonOpts, buttonIndex) {
311386
var button = d3.select(this);
312387

@@ -318,7 +393,7 @@ function drawButtons(gd, gHeader, gButton, menuOpts) {
318393
// skip `dragend` events
319394
if(d3.event.defaultPrevented) return;
320395

321-
setActive(gd, menuOpts, buttonOpts, gHeader, gButton, buttonIndex);
396+
setActive(gd, menuOpts, buttonOpts, gHeader, gButton, scrollBox, buttonIndex);
322397

323398
Plots.executeAPICommand(gd, buttonOpts.method, buttonOpts.args);
324399

@@ -336,49 +411,6 @@ function drawButtons(gd, gHeader, gButton, menuOpts) {
336411
});
337412

338413
buttons.call(styleButtons, menuOpts);
339-
340-
scrollBox.enable();
341-
342-
var active = menuOpts.active,
343-
i;
344-
if(isVertical) {
345-
if(scrollBox._vbar) {
346-
var translateY = 0;
347-
for(i = 0; i < active; i++) {
348-
translateY += menuOpts.heights[i] + constants.gapButton;
349-
}
350-
translateY -= constants.gapButton;
351-
352-
scrollBox.setTranslate(0, translateY);
353-
}
354-
}
355-
else {
356-
if(scrollBox._hbar) {
357-
var translateX = 0;
358-
for(i = 0; i < active; i++) {
359-
translateX += menuOpts.widths[i] + constants.gapButton;
360-
}
361-
translateX -= constants.gapButton;
362-
363-
scrollBox.setTranslate(translateX, 0);
364-
}
365-
}
366-
}
367-
368-
function setActive(gd, menuOpts, buttonOpts, gHeader, gButton, buttonIndex, isSilentUpdate) {
369-
// update 'active' attribute in menuOpts
370-
menuOpts._input.active = menuOpts.active = buttonIndex;
371-
372-
if(menuOpts.type === 'dropdown') {
373-
// fold up buttons and redraw header
374-
gButton.attr(constants.menuIndexAttrName, '-1');
375-
376-
drawHeader(gd, gHeader, gButton, menuOpts);
377-
}
378-
379-
if(!isSilentUpdate || menuOpts.type === 'buttons') {
380-
drawButtons(gd, gHeader, gButton, menuOpts);
381-
}
382414
}
383415

384416
function drawItem(item, menuOpts, itemOpts) {
@@ -611,11 +643,6 @@ function setItemPosition(item, menuOpts, posOpts, overrideOpts) {
611643

612644
function removeAllButtons(gButton) {
613645
gButton.selectAll('g.' + constants.dropdownButtonClassName).remove();
614-
615-
// remove scrollbox
616-
gButton.selectAll('rect.scrollbar-horizontal').remove();
617-
gButton.selectAll('rect.scrollbar-vertical').remove();
618-
gButton.call(Drawing.setClipUrl, null);
619646
}
620647

621648
function clearPushMargins(gd) {

0 commit comments

Comments
 (0)