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:
parent
ab03852234
commit
800b1b060e
|
@ -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">
|
||||
|
|
|
@ -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();
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -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); }
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue