Reflowing floating components.

I've got an html5 video player component in my application that's having trouble being displayed full-screen when executed as a cordova app in android.

In my current setup I ran into two problems:
- One of the ancestors of the video tag has a overflow: hidden; css style. As a result, when the video gets scaled up, part of the video tag is invisible as it overflows that ancestor.
- The video tag is nested in a div with a translate: translateZ(0); css style. As a result, when the video tag gets scaled up, it does not scale beyond the boundaries of the translated ancestor.

Luckily, my video player is not located inside a scroller or anything that will change the player's position relative to the window, so once it's in place, it does not need to move unless the browser window gets resized. So I decided to promote the video player to live in enyo's floatingLayer. This way, when going into full-screen mode, it can scale up to fill the entire screen without interfering with overflows or css translations.

To do this I have a setup as follows:
enyo.kind({
kind: "MyVideoPage",
components: [{
name: "videoTitle",
}, {
name: "content",
components: [{
name: "spacerLeft",
components: [{
name: "backButton"
}]
}, {
name: "playerProxy"
}, {
name: "spacerRight"
}]
}, {
name: "spacerBottom"
}],

...

createPlayer: function() {
this.createComponent({
name: "videoPlayer",
kind: "MyVideoPlayer",
videoUrl: "http://some.cdn.com/my-video.m3u8",
layoutProxyPath: ".owner.$.playerProxy"
});

if (this.hasNode()) {
videoPlayer = this.$.videoPlayer;
videoPlayer.render();
}
},

...

});

enyo.kind({
name: "MyVideoPlayer",
style: "position: absolute;",
floating: true,
layoutProxyPath: undefined,
create: enyo.inherit(function(sup) {
return function() {
sup.apply(this, arguments);
if (this.floating) {
this.addClass("floating");
this.setParent(enyo.floatingLayer);
}
};
}),

...

getLayoutProxyBounds: function() {
var proxy, proxyNode;

proxy = this.layoutProxyPath && this.get(this.layoutProxyPath);
proxyNode = proxy && proxy.hasNode();
return proxyNode ? proxyNode.getBoundingClientRect() : undefined;
},
reflow: function() {
var proxyBounds;

if (this.floating) {
proxyBounds = this.getLayoutProxyBounds();
this.addStyles({
top: proxyBounds.top + "px",
left: proxyBounds.left + "px",
width: proxyBounds.width + "px",
height: proxyBounds.height + "px"
});
}
},

...

});
Now, the problem I run into is that, when I resize my browser, the MyVideoPlayer.reflow method does not get executed. It's as if nothing bubbles the resize event into the video player.

Currently I fixed this by explicitly triggering a reflow on the videoPlayer from inside the MyVideoPage component:
enyo.kind({
kind: "MyVideoPage",

...

reflow: enyo.inherit(function(sup) {
return function() {
var videoPlayer = this.$.videoPlayer;

sup.apply(this, arguments);
videoPlayer && videoPlayer.reflow();
};
}),

...

});
But I'd like to know why the reflow event does not get bubbled to the videoPlayer component in the first place, and if my current fix is the right way to deal with that.

Comments

  • edited January 2015
    One of two things is broken ... either the documentation should recommend (which it can't because the method is private):
    enyo.floatingLayer.addChild(this.$.videoPlayer);
    or the parentChanged handler should do that. As it stands now, setting the parent removes the control from the previous parent but does nothing to notify the new parent it has a new child.

    As a result, when enyo.floatingLayer receives the waterfalled onresize event, it checks its $ hash and children to find potential targets, fails, and returns.
  • A better solution, I think, it to use setContainer instead of setParent, since the former updates the latter. Even better, you can skip the call to setContainer and just include container: enyo.floatingLayer in your components block and setContainer will get called for you.
  • Oh right, now that you say it, I do remember having noticed something about parents not getting notified about their new child.

    Just so I have my mental model of things formed correctly. I usually think of the structure of an enyo application as consisting of 2 trees:
    - A tree defined by owner->child relations mapping the application's control flow.
    - A tree defined by parent->child relations which maps the DOM tree.

    Is there a 3rd tree with container->child relations? Or does the container definition simply map to a parent->child relation such that setting a container ensures that the child knows about it's parent and the parent knows about it's child?
  • Okay, so to answer my own question, setting the container triggers an addChild call on the container, which in turn sets up a proper parent->child relationship where the parent knows about it's child and the child knows about it's parent.
  • I ended up using a different strategy though. Since the videoPlayer component's position on the screen depends on the proxy's position on the screen. This means, the proxy should reflow before the videoPlayer does.

    If I turn the floatingLayer into the container of the videoPlayer, the videoPlayer reflows before the proxy does, thus creating a broken layout.

    Instead I went with the following solution:
    enyo.kind({
    name: "MyVideoPlayer",
    style: "position: absolute;",
    floating: true,
    layoutProxyPath: undefined,
    create: enyo.inherit(function(sup) {
    return function() {
    sup.apply(this, arguments);
    if (this.floating) {
    this.addClass("floating");
    this.setParent(enyo.floatingLayer);
    this.layoutProxyPathChanged();
    }
    };
    }),

    ...

    /**
    ## Floating Layout

    If this player is floating, we can get the boundingClientRectangle of a
    proxy component that resides in the application's non-floating layout
    and use it to properly position the floating player as if it were
    embedded in the non-floating application layout.
    */
    layoutProxyPathChanged: function() {
    this.layoutProxy = this.get(this.layoutProxyPath);

    // Proxy should bubble to the videoPlayer
    this.layoutProxy.set("bubbleTarget", this);

    // If this player is rendered, reflow according to new layout proxy
    if(this.hasNode()) {
    this.reflow();
    }
    },
    //* Retrieves the boundingClientRectangle of the layout proxy.
    getLayoutProxyBounds: function() {
    var proxy = this.layoutProxy,
    proxyNode;

    proxyNode = proxy && proxy.hasNode();

    return proxyNode ? proxyNode.getBoundingClientRect() : undefined;
    },
    reflow: function() {
    var proxyBounds;

    // If we're floating, line up with the proxy component.
    if (this.floating) {
    proxyBounds = this.getLayoutProxyBounds();
    this.addStyles({
    top: proxyBounds.top + "px",
    left: proxyBounds.left + "px",
    width: proxyBounds.width + "px",
    height: proxyBounds.height + "px"
    });
    }
    },

    ...

    });
    This way the floatingLayer is still the videoPlayer's parent, so the player gets rendered inside the floatingLayer. The videoPlayer, however, is not added as it's child, so the floatingLayer does not bubble to the player.

    Instead, the videoPlayer is set as the bubbleTarget of the layoutProxy, so that any events sent to the proxy, will be forwarded to the videoPlayer. This includes the resize event, and thus the videoPlayer gets reflowed after the layoutProxy has reflowed.
  • Ok, that was actually pretty stupid... the above solution does not work. The resize event does not bubble, it waterfalls.. I had another snippet of code that forced a reflow on the floating player.
Sign In or Register to comment.