Skip to content

Commit

Permalink
fix(modal): fixing hack which gets root viewContainerRef to attach ba…
Browse files Browse the repository at this point in the history
…ckdrop

fixes #975, fixes #854
  • Loading branch information
valorkin committed Sep 16, 2016
1 parent b6ce45c commit b5db597
Show file tree
Hide file tree
Showing 2 changed files with 44 additions and 55 deletions.
77 changes: 37 additions & 40 deletions components/modal/modal.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ import {
ElementRef,
EventEmitter,
HostListener,
Injector,
Input,
OnDestroy,
Output,
Expand All @@ -32,49 +31,49 @@ const BACKDROP_TRANSITION_DURATION = 150;
})
export class ModalDirective implements AfterViewInit, OnDestroy {
@Input()
public set config(conf:ModalOptions) {
public set config(conf: ModalOptions) {
this._config = this.getConfig(conf);
};

@Output() public onShow:EventEmitter<ModalDirective> = new EventEmitter<ModalDirective>();
@Output() public onShown:EventEmitter<ModalDirective> = new EventEmitter<ModalDirective>();
@Output() public onHide:EventEmitter<ModalDirective> = new EventEmitter<ModalDirective>();
@Output() public onHidden:EventEmitter<ModalDirective> = new EventEmitter<ModalDirective>();
@Output() public onShow: EventEmitter<ModalDirective> = new EventEmitter<ModalDirective>();
@Output() public onShown: EventEmitter<ModalDirective> = new EventEmitter<ModalDirective>();
@Output() public onHide: EventEmitter<ModalDirective> = new EventEmitter<ModalDirective>();
@Output() public onHidden: EventEmitter<ModalDirective> = new EventEmitter<ModalDirective>();

public get config():ModalOptions {
public get config(): ModalOptions {
return this._config;

}

// seems like an Options
public isAnimated:boolean = true;
public isAnimated: boolean = true;

public get isShown():boolean {
public get isShown(): boolean {
return this._isShown;
}

// todo: implement _dialog
protected _dialog:any;
protected _dialog: any;

protected _config:ModalOptions;
protected _isShown:boolean = false;
protected _config: ModalOptions;
protected _isShown: boolean = false;

private isBodyOverflowing:boolean = false;
private originalBodyPadding:number = 0;
private scrollbarWidth:number = 0;
private isBodyOverflowing: boolean = false;
private originalBodyPadding: number = 0;
private scrollbarWidth: number = 0;

// reference to backdrop component
private backdrop:ComponentRef<ModalBackdropComponent>;
private backdrop: ComponentRef<ModalBackdropComponent>;

private get document():any {
private get document(): any {
return this.componentsHelper.getDocument();
};

/** Host element manipulations */
// @HostBinding(`class.${ClassName.IN}`) private _addClassIn:boolean;

@HostListener('click', ['$event'])
protected onClick(event:any):void {
protected onClick(event: any): void {
if (this.config.ignoreBackdropClick || this.config.backdrop === 'static' || event.target !== this.element.nativeElement) {
return;
}
Expand All @@ -84,19 +83,18 @@ export class ModalDirective implements AfterViewInit, OnDestroy {

// todo: consider preventing default and stopping propagation
@HostListener('keydown.esc')
protected onEsc():void {
protected onEsc(): void {
if (this.config.keyboard) {
this.hide();
}
}

public constructor(private element:ElementRef,
private renderer:Renderer,
private injector:Injector,
private componentsHelper:ComponentsHelper) {
public constructor(private element: ElementRef,
private renderer: Renderer,
private componentsHelper: ComponentsHelper) {
}

public ngOnDestroy():any {
public ngOnDestroy(): any {
this.config = void 0;
// this._element = null
// this._dialog = null
Expand All @@ -107,17 +105,17 @@ export class ModalDirective implements AfterViewInit, OnDestroy {
this.scrollbarWidth = void 0;
}

public ngAfterViewInit():any {
public ngAfterViewInit(): any {
this._config = this._config || this.getConfig();
}

/** Public methods */

public toggle(/*relatedTarget?:ViewContainerRef*/):void {
public toggle(/*relatedTarget?:ViewContainerRef*/): void {
return this._isShown ? this.hide() : this.show(/*relatedTarget*/);
}

public show(/*relatedTarget?:ViewContainerRef*/):void {
public show(/*relatedTarget?:ViewContainerRef*/): void {
this.onShow.emit(this);
if (this._isShown) {
return;
Expand All @@ -137,7 +135,7 @@ export class ModalDirective implements AfterViewInit, OnDestroy {
});
}

public hide(event?:Event):void {
public hide(event?: Event): void {
if (event) {
event.preventDefault();
}
Expand All @@ -161,14 +159,14 @@ export class ModalDirective implements AfterViewInit, OnDestroy {
}

/** Private methods */
private getConfig(config?:ModalOptions):ModalOptions {
private getConfig(config?: ModalOptions): ModalOptions {
return Object.assign({}, modalConfigDefaults, config);
}

/**
* Show dialog
*/
private showElement(/*relatedTarget?:ViewContainerRef*/):void {
private showElement(/*relatedTarget?:ViewContainerRef*/): void {
// todo: replace this with component helper usage `add to root`
if (!this.element.nativeElement.parentNode ||
(this.element.nativeElement.parentNode.nodeType !== Node.ELEMENT_NODE)) {
Expand Down Expand Up @@ -204,7 +202,7 @@ export class ModalDirective implements AfterViewInit, OnDestroy {
}
}

private hideModal():void {
private hideModal(): void {
this.renderer.setElementAttribute(this.element.nativeElement, 'aria-hidden', 'true');
this.renderer.setElementStyle(this.element.nativeElement, 'display', 'none');
this.showBackdrop(() => {
Expand All @@ -218,14 +216,13 @@ export class ModalDirective implements AfterViewInit, OnDestroy {
}

// todo: original show was calling a callback when done, but we can use promise
private showBackdrop(callback?:Function):void {
private showBackdrop(callback?: Function): void {
if (this._isShown && this.config.backdrop) {
this.backdrop = this.componentsHelper
.appendNextToRoot(
ModalBackdropComponent,
ModalBackdropOptions,
new ModalBackdropOptions({animate: false}),
this.injector);
new ModalBackdropOptions({animate: false}));

if (this.isAnimated) {
this.backdrop.instance.isAnimated = this.isAnimated;
Expand Down Expand Up @@ -263,7 +260,7 @@ export class ModalDirective implements AfterViewInit, OnDestroy {
}
}

private removeBackdrop():void {
private removeBackdrop(): void {
if (this.backdrop) {
this.backdrop.destroy();
this.backdrop = void 0;
Expand Down Expand Up @@ -295,19 +292,19 @@ export class ModalDirective implements AfterViewInit, OnDestroy {
// }
// }

private resetAdjustments():void {
private resetAdjustments(): void {
this.renderer.setElementStyle(this.element.nativeElement, 'paddingLeft', '');
this.renderer.setElementStyle(this.element.nativeElement, 'paddingRight', '');
}

/** Scroll bar tricks */

private checkScrollbar():void {
private checkScrollbar(): void {
this.isBodyOverflowing = this.document.body.clientWidth < window.innerWidth;
this.scrollbarWidth = this.getScrollbarWidth();
}

private setScrollbar():void {
private setScrollbar(): void {
if (!this.document) {
return;
}
Expand All @@ -326,12 +323,12 @@ export class ModalDirective implements AfterViewInit, OnDestroy {
}
}

private resetScrollbar():void {
private resetScrollbar(): void {
this.document.body.style.paddingRight = this.originalBodyPadding;
}

// thx d.walsh
private getScrollbarWidth():number {
private getScrollbarWidth(): number {
const scrollDiv = this.renderer.createElement(this.document.body, 'div', void 0);
scrollDiv.className = ClassName.SCROLLBAR_MEASURER;
const scrollbarWidth = scrollDiv.offsetWidth - scrollDiv.clientWidth;
Expand Down
22 changes: 7 additions & 15 deletions components/utils/components-helper.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,19 +38,13 @@ export class ComponentsHelper {
* ```
* @returns {ViewContainerRef} - application root view component ref
*/
public getRootViewContainerRef(_injector:Injector):ViewContainerRef {
public getRootViewContainerRef():ViewContainerRef {
// The only way for now (by @mhevery)
// https://github.com/angular/angular/issues/6446#issuecomment-173459525
// this is a class of application bootstrap component (like my-app)
const classOfRootComponent = this.applicationRef.componentTypes[0];
// this is an instance of application bootstrap component
let appInstance:any;
let injector:any = _injector as any;
while (!appInstance) {
appInstance = injector.get(classOfRootComponent, false);
if (!appInstance && injector.parentInjector) {
injector = injector.parentInjector;
}
const appInstance = this.applicationRef.components[0].instance;
if (!appInstance.viewContainerRef) {
const appName = this.applicationRef.componentTypes[0].name;
throw new Error(`Missing 'viewContainerRef' declaration in ${appName} constructor`);
}
return appInstance.viewContainerRef;
}
Expand Down Expand Up @@ -88,14 +82,12 @@ export class ComponentsHelper {
* @param ComponentClass - @Component class
* @param ComponentOptionsClass - options class
* @param options - instance of options
* @param contextInjector - injector to resolve root view container (any injector except root injector will fit)
* @returns {ComponentRef<T>} - returns ComponentRef<T>
*/
public appendNextToRoot<T>(ComponentClass:Type<T>,
ComponentOptionsClass:any,
options:any,
contextInjector:Injector):ComponentRef<T> {
let location = this.getRootViewContainerRef(contextInjector);
options:any):ComponentRef<T> {
let location = this.getRootViewContainerRef();
let providers = ReflectiveInjector.resolve([
{provide: ComponentOptionsClass, useValue: options}
]);
Expand Down

0 comments on commit b5db597

Please sign in to comment.