fix(aio): autoscroll & add #top-of-page

* Scrolls to hash element or top of page when no hash.
* Scrolls down a bit (80px) to account for top menu overhang.
* No longer scrolls when the hash element is not found.
* Adds `<a id="top-of-page"></a>` which will benefit future efforts to
navigate there from within a page.
This commit is contained in:
Ward Bell 2017-04-17 15:19:15 -07:00 committed by Pete Bacon Darwin
parent ab03852234
commit 800b1b060e
5 changed files with 32 additions and 24 deletions

View File

@ -9,6 +9,7 @@
<span class="fill-remaining-space"></span>
</md-toolbar>
<a id="top-of-page"></a>
<md-sidenav-container class="sidenav-container" role="main">
<md-sidenav [ngClass]="{'collapsed': !isSideBySide }" #sidenav class="sidenav" [opened]="isOpened" [mode]="mode">

View File

@ -286,14 +286,14 @@ describe('AppComponent', () => {
const scrollService: AutoScrollService = fixture.debugElement.injector.get(AutoScrollService);
spyOn(scrollService, 'scroll');
locationService.go('some/url#fragment');
expect(scrollService.scroll).toHaveBeenCalledWith(jasmine.any(HTMLElement));
expect(scrollService.scroll).toHaveBeenCalledWith();
});
it('should be called when a document has been rendered', () => {
const scrollService: AutoScrollService = fixture.debugElement.injector.get(AutoScrollService);
spyOn(scrollService, 'scroll');
component.onDocRendered();
expect(scrollService.scroll).toHaveBeenCalledWith(jasmine.any(HTMLElement));
expect(scrollService.scroll).toHaveBeenCalledWith();
});
});

View File

@ -106,7 +106,7 @@ export class AppComponent implements OnInit {
// Scroll to the anchor in the hash fragment.
autoScroll() {
this.autoScrollService.scroll(this.docViewer.nativeElement.offsetParent);
this.autoScrollService.scroll();
}
onDocRendered() {

View File

@ -3,11 +3,9 @@ import { PlatformLocation } from '@angular/common';
import { DOCUMENT } from '@angular/platform-browser';
import { AutoScrollService } from './auto-scroll.service';
describe('AutoScrollService', () => {
let injector: ReflectiveInjector,
autoScroll: AutoScrollService,
container: HTMLElement,
location: MockPlatformLocation,
document: MockDocument;
@ -16,6 +14,7 @@ describe('AutoScrollService', () => {
}
class MockDocument {
body = new MockElement();
getElementById = jasmine.createSpy('Document getElementById');
}
@ -23,6 +22,10 @@ describe('AutoScrollService', () => {
scrollIntoView = jasmine.createSpy('Element scrollIntoView');
}
beforeEach(() => {
spyOn(window, 'scrollBy');
});
beforeEach(() => {
injector = ReflectiveInjector.resolveAndCreate([
AutoScrollService,
@ -31,34 +34,37 @@ describe('AutoScrollService', () => {
]);
location = injector.get(PlatformLocation);
document = injector.get(DOCUMENT);
container = window.document.createElement('div');
container.scrollTop = 100;
autoScroll = injector.get(AutoScrollService);
});
it('should scroll the container to the top if there is no hash', () => {
it('should scroll to the top if there is no hash', () => {
location.hash = '';
autoScroll.scroll(container);
expect(container.scrollTop).toEqual(0);
const topOfPage = new MockElement();
document.getElementById.and
.callFake(id => id === 'top-of-page' ? topOfPage : null);
autoScroll.scroll();
expect(topOfPage.scrollIntoView).toHaveBeenCalled();
});
it('should scroll the container to the top if the hash does not match an element id', () => {
location.hash = 'some-id';
it('should not scroll if the hash does not match an element id', () => {
location.hash = 'not-found';
document.getElementById.and.returnValue(null);
autoScroll.scroll(container);
expect(document.getElementById).toHaveBeenCalledWith('some-id');
expect(container.scrollTop).toEqual(0);
autoScroll.scroll();
expect(document.getElementById).toHaveBeenCalledWith('not-found');
expect(window.scrollBy).not.toHaveBeenCalled();
});
it('should scroll the container to the element whose id matches the hash', () => {
it('should scroll to the element whose id matches the hash', () => {
const element = new MockElement();
location.hash = 'some-id';
document.getElementById.and.returnValue(element);
autoScroll.scroll(container);
autoScroll.scroll();
expect(document.getElementById).toHaveBeenCalledWith('some-id');
expect(element.scrollIntoView).toHaveBeenCalled();
expect(window.scrollBy).toHaveBeenCalledWith(0, -80);
});
});

View File

@ -7,22 +7,23 @@ import { DOCUMENT } from '@angular/platform-browser';
*/
@Injectable()
export class AutoScrollService {
constructor(
@Inject(DOCUMENT) private document: any,
private location: PlatformLocation) { }
/**
* Scroll the contents of the container
* to the element with id extracted from the current location hash fragment
* Scroll to the element with id extracted from the current location hash fragment
* Scroll to top if no hash
* Don't scroll if hash not found
*/
scroll(container: HTMLElement) {
scroll() {
const hash = this.getCurrentHash();
const element: HTMLElement = this.document.getElementById(hash);
const element: HTMLElement = hash
? this.document.getElementById(hash)
: this.document.getElementById('top-of-page') || this.document.body;
if (element) {
element.scrollIntoView();
} else {
container.scrollTop = 0;
if (window && window.scrollBy) { window.scrollBy(0, -80); }
}
}