Skip to content

Commit fcfd772

Browse files
committed
Automatically scroll to the selected page in the new sidebar
When the page first loads or the user clicks a link in the content, the sidebar will scroll to the selected page so that it’s visible. Closes #339
1 parent 6b531d3 commit fcfd772

File tree

12 files changed

+111
-23
lines changed

12 files changed

+111
-23
lines changed

package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
"can-define": "^1.5.1",
3737
"can-stache": "^3.10.0",
3838
"can-util": "^3.0.10",
39+
"can-view-callbacks": "^3.2.2",
3940
"escape-html": "^1.0.3",
4041
"flexibility": "^2.0.1",
4142
"jquery": "^3.1.1",
@@ -50,6 +51,7 @@
5051
"bit-docs-generate-searchmap": "bit-docs/bit-docs-generate-searchmap#sidebar",
5152
"bit-docs-html-toc": "^0.6.0",
5253
"connect": "^3.5.0",
54+
"funcunit": "^3.4.3",
5355
"normalize.css": "^5.0.0",
5456
"q": "^1.4.1",
5557
"steal": "^1.5.9",

sidebar/demo.less

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,16 @@
1+
#demo-controls {
2+
float: left;
3+
4+
button {
5+
clear: left;
6+
float: left;
7+
}
8+
}
9+
110
#everything {
2-
height: 75vh;
3-
margin: 5vh auto;
11+
float: left;
12+
height: 100vh;
13+
margin: 0;
414
outline: 1px solid lightgray;
515
width: 330px;
616
}

sidebar/demo.stache

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,3 @@
1-
{{#each pages page=value}}
2-
<button on:click="setSelectedPageName(page.name)">
3-
{{page.name}}
4-
</button>
5-
{{/each}}
6-
71
<div id="everything">
82
<div class="column" id="left">
93
<div class="bottom">
@@ -15,3 +9,11 @@
159
</div>
1610
</div>
1711
</div>
12+
13+
<div id="demo-controls">
14+
{{#each pages page=value}}
15+
<button class="go-to-{{page.name}}" on:click="setSelectedPageName(page.name)">
16+
{{page.name}}
17+
</button>
18+
{{/each}}
19+
</div>

sidebar/sidebar.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ var events = require('./sidebar.events');
33
var template = require('./sidebar.stache!steal-stache');
44
var ViewModel = require('./sidebar.viewmodel');
55

6+
require('./view-callbacks');
67
require('../static/canjs.less!steal-less');
78

89
module.exports = Component.extend({

sidebar/sidebar.stache

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,9 @@
1010
</li>
1111
{{else}}
1212
<li class="
13-
{{#eq child selectedPage}}current{{/eq}}
1413
{{#if isExpanded(child)}}expanded{{/if}}
1514
{{#if child.sortedChildren.length}}parent{{/if}}
16-
">
15+
" {{#eq child selectedPage}}selected-in-sidebar{{/eq}}>
1716
<a {{data "page"}}
1817
class="{{child.type}}"
1918
href="{{pathPrefix}}{{child.url}}"

sidebar/test.html

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,9 @@
66
</head>
77
<body>
88
<div id="qunit-fixture"></div>
9+
<script>
10+
FuncUnit = { frameMode: true };
11+
</script>
912
<script src="../node_modules/steal/steal.js" main="bit-docs-html-canjs/sidebar/test"></script>
1013
</body>
1114
</html>

sidebar/test.js

Lines changed: 38 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
1+
var FuncUnit = require('funcunit');
12
var localStorage = require('./local-storage');
23
var PageModel = require('./page-model');
34
var QUnit = require('steal-qunit');
45
var searchMap = require('../doc/searchMap.json');
56
var stache = require('can-stache');
7+
var utils = require('./utils');
68
var ViewModel = require('./sidebar.viewmodel');
79

810
require('./sidebar');
@@ -159,7 +161,7 @@ QUnit.test('Correct page is selected after “asynchonously” setting the searc
159161
var vm = new ViewModel({selectedPageName: 'about'});
160162
vm.searchMap = searchMap;
161163
var fragment = renderer(vm);
162-
var currentPage = fragment.querySelector('.current');
164+
var currentPage = fragment.querySelector('[selected-in-sidebar]');
163165
assert.ok(currentPage, 'page is selected when searchMap is provided');
164166
var currentPageTitle = currentPage.querySelector('a').textContent.trim();
165167
assert.strictEqual('About', currentPageTitle, 'correct page is selected');
@@ -173,7 +175,7 @@ QUnit.test('When an item is selected, its children should be shown', function(as
173175
firstLink.click();
174176
var firstLinkParent = firstLink.parentElement;
175177
var firstLinkParentChildrenLinks = firstLinkParent.querySelectorAll('li');
176-
assert.ok(firstLinkParent.classList.contains('current'), 'has current class');
178+
assert.ok(firstLinkParent.hasAttribute('selected-in-sidebar'), 'has selected-in-sidebar attribute');
177179
assert.ok(firstLinkParent.classList.contains('expanded'), 'has expanded class');
178180
assert.ok(firstLinkParentChildrenLinks.length > 0, 'has children');
179181
});
@@ -197,3 +199,37 @@ QUnit.test('When a child item is selected, it should still be visible', function
197199
assert.ok(firstLinkParent.classList.contains('expanded'), 'parent has expanded class');
198200
assert.ok(firstLinkParentChildrenLinks.length > 0, 'parent has children');
199201
});
202+
203+
QUnit.test('Sidebar scrolls to selected items', function(assert) {
204+
var done = assert.async(1);
205+
206+
var timeout = 20000;
207+
var timeoutID = setTimeout(function() {
208+
assert.notOk(true, 'Test took longer than ' + timeout + 'ms; test timed out.');
209+
}, timeout);
210+
211+
// Open the demo page in a new window
212+
FuncUnit.open('../sidebar/demo.html', function() {
213+
214+
// Set the height & width of FuncUnit’s iframe
215+
FuncUnit.frame.height = 200;
216+
FuncUnit.frame.width = 600;
217+
218+
// Select the can-ajax page
219+
FuncUnit('.go-to-can-ajax').click();
220+
221+
// Check to make sure the element is visible
222+
FuncUnit('[selected-in-sidebar]').wait(function() {
223+
var element = this[0];
224+
if (!element) {
225+
return false;
226+
}
227+
var rect = element.getBoundingClientRect();
228+
return utils.rectIntersectsWithWindow(rect, FuncUnit.win);
229+
}, function() {
230+
clearTimeout(timeoutID);
231+
assert.ok(true, 'did scroll to selected element');
232+
done();
233+
});
234+
});
235+
});

sidebar/utils.js

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
module.exports = {
2+
rectIntersectsWithWindow: function(rect, elementWindow) {
3+
return (
4+
rect.top >= this.safeInset.top &&
5+
rect.top <= (elementWindow.innerHeight - this.safeInset.bottom) &&
6+
rect.left >= 0 &&
7+
rect.left <= elementWindow.innerWidth
8+
);
9+
},
10+
safeInset: {
11+
bottom: 49,
12+
top: 101
13+
}
14+
};

sidebar/view-callbacks.js

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
var canViewCallbacks = require('can-view-callbacks');
2+
var utils = require('./utils');
3+
4+
canViewCallbacks.attr('selected-in-sidebar', function(element) {
5+
var containerElement = document.querySelector('#left .bottom');
6+
if (!containerElement) {
7+
return;
8+
}
9+
10+
// Using rAF because otherwise getBoundingClientRect won’t return useful values
11+
requestAnimationFrame(function() {
12+
var elementRect = element.getBoundingClientRect();
13+
14+
// Only scroll if the element isn’t in the viewport
15+
if (utils.rectIntersectsWithWindow(elementRect, window) === false) {
16+
var parent = element.parentElement;
17+
var parentScrollTop = 0;
18+
while (parent) {
19+
parentScrollTop += parent.scrollTop;
20+
parent = parent.parentElement;
21+
}
22+
containerElement.scrollTop = elementRect.top + parentScrollTop - utils.safeInset.top;
23+
}
24+
});
25+
});

static/canjs.js

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -88,8 +88,6 @@ var $articleContainer,
8888
toggleNav();
8989
}, 200);
9090
}
91-
92-
scrollToCurrentMenuItem();
9391
})();
9492

9593
// Touch support
@@ -173,13 +171,6 @@ function init() {
173171
hasShownSearch = true;
174172
}
175173

176-
function scrollToCurrentMenuItem(){
177-
var currentPageLi = $('li.current');
178-
if(currentPageLi.length){
179-
$('.nav-menu').scrollTop(currentPageLi.offset().top - $('.nav-menu').offset().top);
180-
}
181-
}
182-
183174
function setPathPrefix(){
184175
var pathPrefix;
185176
if($pathPrefix && $pathPrefix.length){

0 commit comments

Comments
 (0)