Pregunta

I am trying to create a custom web component with the following characteristics:

  • Can have multiple child <li> elements (like <ul> and <ol>)
  • Has a custom layout algorithm of child <li> elements. To be precise, I would like to layout the <li> elements depending on its rendered size, so I need a way to measure the size of each child.

I've been experimenting with CSS and polymer.dart, but right now I'm pretty much stuck. Is there any way to create such element using polymer.dart?

The current implementation renders like the following image:

Imgur

Each box is an <li> element with an absolute coordinate. The boxes are currently positioned randomly, but what I really want to do is layout each box so they do not overlap each other.

The current problem is that I cannot measure the size of each child until they are actually rendered. I tried using hidden dummy elements to measure the size beforehand, but it seems polymer doesn't update the DOM until afterwards, so I can't even use dummy elements.

The current implementation can be found at https://github.com/rillomas/xclamm-gae/tree/51061a3bf33fe15724103517437d3281fe6ec495/web/lib/components . The components I'm experimenting with are <remark-presenter> (the custom <ul> component) and <remark-view> (the custom <li> component).

¿Fue útil?

Solución

Thanks to Günter, I was able to implement a custom <ul> (a <ul> wrapper web component to be precise) using MutationObserver and it worked perfectly.

I made a sample project that demonstrates how I used MutationObserver with Polymer.dart if anyone is intrested.

https://github.com/rillomas/custom-list

In the sample, I used MutationObserver like the following:

@override
void enteredView() {
    super.enteredView();
    _root = shadowRoot.querySelector("#root");

    // observe change of child elements for a 2 pass layout approach
    var mo = new MutationObserver(onChildMutation);
    mo.observe(_root, childList:true);
    _observer = mo;
}

/// Called when a mutation occurs to the target element
void onChildMutation(List<MutationRecord> changes, MutationObserver observer) {
    // layout children depending on its rendering size

    int itemCount = 0;
    int bounceBackDelta = bouncebacknum - 1;
    int nextBounceBack = bounceBackDelta;
    int xOffset = 0;
    int yOffset = 0;
    bool offsetRight = true;
    for (var record in changes) {
        var added = record.addedNodes;
        var removed = record.removedNodes;
        for (var a in added) {
            if (!(a.nodeType == Node.ELEMENT_NODE && a is CustomListItem)) {
                // ignore nodes expect for the one we want
                continue;
            }
            var elem = a as CustomListItem;
            var width = elem.clientWidth;
            var height = elem.clientHeight;
            // print("${width}x${height}");

            var unit = "px";
            if (elem.item.entered) {
                // This item is already displayed
                // so place the view at the same position

                var l = "${elem.item.rect.left}${unit}";
                var t = "${elem.item.rect.top}${unit}";
                elem.style.left = l;
                elem.style.top = t;
            } else {
                // This item is a new one.
                //  Layout the remark at a sufficient position
                Rectangle<int> posRect;
                if (offsetRight) {
                    posRect = new Rectangle<int>(xOffset,yOffset,width, height);
                } else {
                    posRect = new Rectangle<int>(xOffset-width, yOffset, width, height);
                }
                var l = "${posRect.left}${unit}";
                var t = "${posRect.top}${unit}";
                elem.style.left = l;
                elem.style.top = t;
                elem.item.rect = posRect;
                applyAnimation(elem, "fadein", 250);
                elem.item.entered = true;
            }

            // update offset depending on direction
            if (offsetRight) {
                xOffset += width;
            } else {
                xOffset -= width;
            }

            // update bounceback state
            if (offsetRight && itemCount == nextBounceBack) {
                offsetRight = false;
                xOffset -= width;
                nextBounceBack += bounceBackDelta;
            } else if (!offsetRight && itemCount == nextBounceBack) {
                offsetRight = true;
                xOffset += width;
                nextBounceBack += bounceBackDelta;
            }

            yOffset += height;
            itemCount++;
        }
    }
}

Otros consejos

The element doesn't have a size until it got rendered.
You can still modify the size after the element got rendered.

I deleted my comment, but I still think that this has nothing to do with Polymer.

EDIT according to your comment Ok, that is more related to Polymer. You can't expect from someone here on StackOverflow to examine you project. You should provide a minimal example that allows to reproduce your problem not a link to a repository containing an entire project.

I think a two 2-pass layout is your only option. Provided this is your approach:

  • you could query your elements and update the fields your HTML is bound to. this may be tricky as they are genereated using template repeat. Maybe mutationObservers are of good use here (haven't used it myself yet).
  • you could try a binding in the repeated li that calls a method of your component with it's model (Remark) so you can identify it and it's size as parameter (no idea if that works) and update your remarkLists positions accordingly. If the object storing the position is observable this should initiate a rerender. You have to be cautious that this doesn't lead to endless update loops.
Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top