Last active
September 22, 2017 15:35
-
-
Save SpeedoPasanen/8f0bd518c82e707770915018691acdd1 to your computer and use it in GitHub Desktop.
FrameComponent
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { Router } from '@angular/router'; | |
import { ChangeDetectorRef, Component, ElementRef, OnDestroy, OnInit, ViewChild, Input } from '@angular/core'; | |
import * as $ from 'jquery'; // ToDo: Get rid of jQuery to comply with Angular Universal. Or just don't server-render the frame, cause what's the point anyway. | |
import { Observable } from 'rxjs/Observable'; | |
import { Subscription } from 'rxjs/Subscription'; | |
import { StateService } from '../../state/state.service'; | |
import * as nodeUrl from 'url'; | |
@Component({ | |
selector: 'm-frame', | |
template: ` | |
<div class="frame-component"> | |
<div *ngIf="loading" class="loading"><i class="fa fa-spin fa-spinner fa-3x"></i></div> | |
<iframe #iframe></iframe> | |
</div> | |
`, | |
styleUrls: ['./frame.component.css'] | |
}) | |
export class FrameComponent implements OnInit, OnDestroy { | |
private _src: string; | |
private subs: Subscription[] = []; | |
public loading = false; | |
@ViewChild('iframe') _iframe: ElementRef; | |
constructor( | |
private router: Router, | |
protected cdRef: ChangeDetectorRef, | |
private state: StateService | |
) { } | |
ngOnInit() { | |
$(this.iframe).on('load', () => { | |
this.loaded(); | |
}); | |
this.subs.push(Observable.fromEvent(window, 'message').subscribe(evt => { | |
// Let's change queryParams.frame when a navigation occurs inside the frame. | |
// All inner routes trigger a window.postMessage at load with the URL as payload. | |
this.receiveMsg(evt); | |
})); | |
this.subs.push(Observable.fromEvent(window, 'resize').debounceTime(300).subscribe(evt => { | |
this.updateHeight(); // I want the frame to fill the available height. ToDo: use flexbox x) | |
})); | |
} | |
ngOnDestroy() { | |
this.subs.forEach(sub => sub.unsubscribe()); | |
$(this.iframe).off('load'); | |
$(window).off('message', this.receiveMsg); | |
} | |
private get iframe(): HTMLIFrameElement { return this._iframe.nativeElement; } | |
private receiveMsg(evt) { | |
if (evt.data && evt.data.loaded) { | |
const src = evt.data.loaded.replace(/^\//, '').replace(/\/$/, ''); | |
if (this._src !== src) { | |
this.router.navigate([], { queryParams: | |
src.length | |
? { frame: src } | |
: {} // Instead of showing the old front page, just leave the frame. | |
}); | |
} | |
return; | |
} | |
} | |
private loaded() { | |
this.updateHeight(); | |
this.loading = false; | |
this.iframe.contentWindow.postMessage({ func: 'initFrame' }, '*'); | |
this.cdRef.markForCheck(); | |
} | |
@Input() public set src(src: string) { | |
if (src === this._src) { | |
return; | |
} | |
this.loading = true; | |
this._src = src; | |
this.iframe.src = '/' + this.getFrameSrc(src); | |
this.cdRef.markForCheck(); | |
} | |
public get src(): string { return this._src; } | |
//// Less interesting stuff. Resize the frame, add query params. | |
/* | |
Add some state info as query params to the frame src, | |
so for example, the same customer can be pre-selected inside the frame that's already selected outside it. | |
*/ | |
public getFrameSrc(urlStr: string) { | |
const urlObj = nodeUrl.parse(urlStr, true); | |
urlObj.query = { customerId: this.state.state.customerId }; | |
if (urlObj.search && urlObj.search.length) { | |
const pairs = urlObj.search.replace(/^\?/, '').split('&'); | |
pairs.forEach(pair => { | |
const parts = pair.split('='); | |
urlObj.query[parts[0]] = parts[1]; | |
}); | |
urlObj.search = null; | |
} | |
return nodeUrl.format(urlObj); | |
} | |
public updateHeight() { | |
const padding = this.getPadding($(this.iframe).parent()); | |
const newHeight = Math.max(this.windowHeight(), $(document).height()) - padding - this.getOffset(); | |
this.setHeight(newHeight - 10); | |
} | |
private getPadding($elem, levelsDown = 0): number { | |
if ((levelsDown > 5) || ($elem.is('body'))) { | |
return 0; | |
} | |
const result = parseInt($elem.css('margin-bottom')) + parseInt($elem.css('padding-bottom')); | |
return result + this.getPadding($elem.parent(), levelsDown + 1); | |
} | |
private setHeight(n: number) { | |
$(this.iframe).height(n); | |
} | |
private getOffset() { | |
return $(this.iframe).offset().top; | |
} | |
private windowHeight(): number { | |
return Math.max(document.documentElement.clientHeight, window.innerHeight || 0); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment