0

I've got an Angular2 application iframed in Liferay portlet. Liferay tomcat is in different domain than angular2 application. I'm trying to resize iframe based on height of iframed application. I'm using helper file on Liferay's tomcat to do this - based on answer Resizing an iframe based on content.

This is what I've got so far: helper.html on tomcat with parent application:

<html>
<!-- 
This page is on the same domain as the parent, so can
communicate with it to order the iframe window resizing
to fit the content 
-->
<body onload="parentIframeResize()">
<script>
    // Tell the parent iframe what height the iframe needs to be
    function parentIframeResize()
    {
        var height = getParam('height');
        // This works as our parent's parent is on our domain..
        parent.parent.resizeIframe(height);
    }

    // Helper function, parse param from request string
    function getParam( name )
    {
        name = name.replace(/[\[]/,"\\\[").replace(/[\]]/,"\\\]");
        var regexS = "[\\?&]"+name+"=([^&#]*)";
        var regex = new RegExp( regexS );
        var results = regex.exec( window.location.href );
        if( results == null )
            return "";
        else
            return results[1];
    }
</script>
</body>
</html>

Jsp file in portlet with iframe:

<iframe id="iframed-application"
        src="${baseUrl}:${externalTomcatPort}/${applicationName}/iframe?baseUrl=${baseUrl}:${port}">
</iframe>

<script>
    // Resize iframe to full height
    function resizeIframe(height) {
        document.getElementById('iframed-application').height = parseInt(height) + 10;
    }
</script>

Angular2 app.component.html

<menu></menu>
<router-outlet></router-outlet>
<iframe id="helpframe" [src]='helperSource' height='0' width='0' frameborder='0'></iframe>

Angular2 app.component.ts

import {Component} from "@angular/core";
import {OnInit} from "@angular/core";
import {ContextService} from "./common/service/context.service";
import {DomSanitizer} from "@angular/platform-browser";

@Component({
    selector: 'app-root',
    templateUrl: './app.component.html',
    styleUrls: ['./app.component.css']
})
export class AppComponent implements OnInit {
    helperSource;

    constructor(private sanitizer: DomSanitizer, private context: ContextService) {
    }

    ngOnInit(): void {
        let height = window.innerHeight;
        if (height < 800) height = 800;
        this.helperSource = this.sanitizer.bypassSecurityTrustResourceUrl(this.context.baseUrl + '/helper/helper.html' + '?height=' + height);
    }
}

When ngOnInit is invoked template is not rendered yet, so it always sets iframe height to 800px, but main question is different. How can I detect resize event of content of angular app in iframe? I've tried to use:

@HostListener('window:resize', ['$event'])

but it's listening to resize of whole window, not the iframe.

My second try was like following:

let iframe: HTMLIFrameElement = <HTMLIFrameElement> document.getElementById('iframed-application');
    let contentWindow: Window = iframe.contentWindow;
    contentWindow.onresize = event => {

};

But it failed also, because document.getElementById returned null - as I understand from security reasons - cross-domain.

Is it anyhow possible to listen to resize of iframed application, or maybe there is another way?

Community
  • 1
  • 1
mdziob
  • 1,084
  • 10
  • 21

2 Answers2

2

Here is a directive that does it

    import {Directive, ElementRef, OnInit, Renderer} from "@angular/core";

@Directive({
    selector: "[iframeAutoHeight]"
})
export class IframeAutoHeightDirective implements OnInit {
    private el: any;
    private renderer: Renderer;
    private prevHeight: number;
    private sameCount: number;

    constructor(_elementRef: ElementRef, _renderer: Renderer) {
        this.el = _elementRef.nativeElement;
        this.renderer = _renderer;
    }

    ngOnInit() {
        const self = this;
        if (this.el.tagName === "IFRAME") {
            this.renderer.listen(this.el, "load", () => {
                self.prevHeight = 0;
                self.sameCount = 0;
                setTimeout(() => {
                    self.setHeight();
                }, 50);
            });
        }
    }

    setHeight() {
        const self = this;
        if (this.el.contentWindow.document.body.scrollHeight !== this.prevHeight) {
            this.sameCount = 0;
            this.prevHeight = this.el.contentWindow.document.body.scrollHeight;
            this.renderer.setElementStyle(
                self.el,
                "height",
                this.el.contentWindow.document.body.scrollHeight + "px"
            );
            setTimeout(() => {
                self.setHeight();
            }, 50);

        } else {
            this.sameCount++;
            if (this.sameCount < 2) {
                setTimeout(() => {
                    self.setHeight();
                }, 50);
            }
        }
    }
}

Usage:

<iframe [src]="iframeUrl" iframeAutoHeight></iframe>
1

Only solution we found is binding this to onclick event on body, example below:

let body =  document.getElementsByTagName('body')[0];

 setTimeout( () => {
     this.setHeight(body.offsetHeight);
 }, 500);

 body.addEventListener('click', () => {
     this.setHeight(body.offsetHeight);
 });
mdziob
  • 1,084
  • 10
  • 21