com.zipsoft.widgets.client.lazylayout.VLazyLayout.java Source code

Java tutorial

Introduction

Here is the source code for com.zipsoft.widgets.client.lazylayout.VLazyLayout.java

Source

/*
 * Copyright 2012 Vaadin Ltd.
 * 
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
 * use this file except in compliance with the License. You may obtain a copy of
 * the License at
 * 
 * http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations under
 * the License.
 */

package com.zipsoft.widgets.client.lazylayout;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.logging.Logger;

import com.google.gwt.core.client.Scheduler;
import com.google.gwt.core.client.Scheduler.ScheduledCommand;
import com.google.gwt.event.dom.client.ScrollEvent;
import com.google.gwt.event.dom.client.ScrollHandler;
import com.google.gwt.event.shared.HandlerRegistration;
import com.google.gwt.user.client.DOM;
import com.google.gwt.user.client.Element;
import com.google.gwt.user.client.Event;
import com.google.gwt.user.client.Timer;
import com.google.gwt.user.client.Window;
import com.google.gwt.user.client.ui.FlowPanel;
import com.google.gwt.user.client.ui.HTML;
import com.google.gwt.user.client.ui.SimplePanel;
import com.google.gwt.user.client.ui.Widget;
import com.vaadin.client.ui.TouchScrollDelegate;
import com.vaadin.client.ui.TouchScrollDelegate.TouchScrollHandler;

public class VLazyLayout extends SimplePanel {

    public static final String TAGNAME = "lazylayout";
    public static final String CLASSNAME = "v-" + TAGNAME;

    static final boolean DEBUG = true;

    private static Logger getLogger() {
        return Logger.getLogger(VLazyLayout.class.getName());
    }

    public interface ComponentFetcher {
        void fetchIndices(List<Integer> indicesToFetch);
    }

    /**
     * U ovom panelu se pojavljuju sve child komponente
     * @author marko
     *
     */
    public static class FlowPane extends FlowPanel {

        public FlowPane() {
            super();
            setStyleName(CLASSNAME + "-container");
        }

        void addOrMove(final Widget child, final int index) {

            if (child.getParent() == this) {
                final int currentIndex = getWidgetIndex(child);
                if (index == currentIndex) {
                    return;
                }
            }
            insert(child, index);
        }
    }

    /**
     * Placeholder widget - U njemu se pojavljuje smao ono LOADING....
     * Sadrzaj se podesava u css-u
     * @author marko
     *
     */
    private static class PlaceholderWidget extends HTML {

        public PlaceholderWidget(final String placeholderWidth, final String placeholderHeight) {
            setWidth(placeholderWidth);
            setHeight(placeholderHeight);
            setStyleName(CLASSNAME + "-placeholder");
            Element message = DOM.createSpan();
            message.setInnerText("U?itavam...");
            message.addClassName(CLASSNAME + "-placeholder-message");
            getElement().appendChild(message);
        }
    }

    /**
     * Tajmer za fecovanje sa servera
     * @author marko
     *
     */
    private class SecondaryFetchTimer extends Timer {
        private static final double SECONDARY_MULTIPLIER = 2d;
        private static final int AMOUNT_OF_TIME_TO_GO_BACK_TO_THE_SERVER = 2;
        private static final int SECONDARY_TIMER = 2000;

        private int visitsLeftToTheServer = AMOUNT_OF_TIME_TO_GO_BACK_TO_THE_SERVER;

        @Override
        public void run() {

            boolean foundSomething = false;
            while (visitsLeftToTheServer > 0 && !foundSomething) {
                final int totalExtraHeight = (int) (getFetchDistancePx() * SECONDARY_MULTIPLIER);
                final double progress = (double) AMOUNT_OF_TIME_TO_GO_BACK_TO_THE_SERVER
                        / (double) visitsLeftToTheServer;
                final int fetchingDistance = (int) (getFetchDistancePx() + (totalExtraHeight * progress));

                foundSomething = findAllThingsToFetchAndFetchThem(fetchingDistance);

                visitsLeftToTheServer--;
                if (foundSomething) {
                    schedule(SECONDARY_TIMER);
                    return;
                }
            }

            resetCounter();
        }

        private void resetCounter() {
            visitsLeftToTheServer = AMOUNT_OF_TIME_TO_GO_BACK_TO_THE_SERVER;
        }

        public void scheduleNew() {
            cancel();
            schedule(SECONDARY_TIMER);
            resetCounter();
        }
    }

    private final FlowPane panel = new FlowPane();
    private final Element margin = DOM.createDiv();

    private int totalAmountOfComponents = 0;
    private String placeholderWidth = "100%";
    private String placeholderHeight = "400px";

    private HandlerRegistration scrollHandlerRegistration;
    private HandlerRegistration scrollHandlerRegistrationWin;
    private HandlerRegistration scrollHandlerRegistrationContainer;

    private final Timer scrollPoller = new Timer() {
        @Override
        public void run() {
            findAllThingsToFetchAndFetchThem(getFetchDistancePx());
            secondaryLoader.scheduleNew();
        }
    };
    private final SecondaryFetchTimer secondaryLoader = new SecondaryFetchTimer();
    private double distanceMultiplier;
    private int pageHeight;
    private int renderDelay;
    private boolean scrollingWasProgrammaticallyAdjusted = false;

    private ComponentFetcher fetcher = null;

    /**
     * @see VLazyLayout#updateScrollAdjustmentReference()
     */
    private int scrollOffsetToReferenceWidgetPx = -1;
    private Widget referenceWidget;

    private TouchScrollHandler touchScrollHandler;

    /**
     * Default konstruuktor
     */
    public VLazyLayout() {
        super();
        getElement().appendChild(margin);
        setStyleName(CLASSNAME);
        margin.setClassName(CLASSNAME + "-margin");
        setWidget(panel);

        DOM.sinkEvents(this.panel.getElement(), Event.ONSCROLL | Event.TOUCHEVENTS);

        this.panel.getElement().getStyle().setProperty("position", "relative");

        //        addStyleName("v-scrollable");

        makeScrollable();
    }

    private void makeScrollable() {
        if (touchScrollHandler == null) {
            touchScrollHandler = TouchScrollDelegate.enableTouchScrolling(this);
        }
        touchScrollHandler.addElement(getElement());
    }

    @Override
    protected Element getContainerElement() {
        return margin;
    }

    private static void debug(final String msg) {
        if (DEBUG) {
            getLogger().info("[LazyLayout] " + msg);
        }
    }

    public void setAmountOfComponents(final int newAmountOfComponents) {

        debug("New amount of components: " + newAmountOfComponents);
        if (newAmountOfComponents != totalAmountOfComponents) {
            if (newAmountOfComponents < totalAmountOfComponents) {
                // TODO
                debug("LazyLayout doesn't support removing of components");
            }

            for (int i = totalAmountOfComponents; i < newAmountOfComponents; i++) {
                panel.add(new PlaceholderWidget(placeholderWidth, placeholderHeight));
            }

            totalAmountOfComponents = newAmountOfComponents;
        }
    }

    /**
     * Podesavanje velicine placeholder komponente
     * @param placeholderHeight
     * @param placeholderWidth
     */
    public void setPlaceholderSize(final String placeholderHeight, final String placeholderWidth) {
        this.placeholderHeight = placeholderHeight;
        this.placeholderWidth = placeholderWidth;
    }

    @Override
    protected void onAttach() {
        super.onAttach();
        refreshPageHeight();
        Scheduler.get().scheduleDeferred(new ScheduledCommand() {
            @Override
            public void execute() {
                VLazyLayout.this.findAllThingsToFetchAndFetchThem();
            }
        });
    }

    @Override
    protected void onDetach() {
        removeScrollHandlers();
        super.onDetach();
    }

    /**
     * Root Widget bi trebalo da bude ovaj widget - Dodeljuje se u konektoru
     * @param rootWidget
     */
    public void attachScrollHandlersIfNeeded(final Widget rootWidget) {
        if (scrollHandlerRegistration == null) {
            // Cannot use Window.addWindowScrollHandler() in Vaadin apps,
            // but we must listen for scroll events in the VView instance
            // instead...
            final ScrollHandler handler = new ScrollHandler() {
                @Override
                public void onScroll(final ScrollEvent event) {
                    startScrollLoad();
                    //                    debug("On scroll - root widget");
                }
            };
            scrollHandlerRegistration = rootWidget.addDomHandler(handler, ScrollEvent.getType());
        }

        //Bas me briga za ceo Prozor
        //        if (scrollHandlerRegistrationWin == null) {
        //            // ...but within embedded apps (portlet) we do actually scroll
        //            // the Window, so we need also the ScrollHandler for the Window.
        //            final Window.ScrollHandler handler = new Window.ScrollHandler() {
        //                @Override
        //                public void onWindowScroll(final com.google.gwt.user.client.Window.ScrollEvent event) {
        //                    startScrollLoad();
        //                    debug("On scroll - window");
        //                }
        //
        //            };
        //            scrollHandlerRegistrationWin = Window.addWindowScrollHandler(handler);
        //        }

    }

    private void removeScrollHandlers() {
        if (scrollHandlerRegistration != null) {
            scrollHandlerRegistration.removeHandler();
        }
        if (scrollHandlerRegistrationWin != null) {
            scrollHandlerRegistrationWin.removeHandler();
        }
        if (scrollHandlerRegistrationContainer != null) {
            scrollHandlerRegistrationContainer.removeHandler();
        }
        scrollPoller.cancel();
        secondaryLoader.cancel();
    }

    private void startScrollLoad() {
        if (!scrollingWasProgrammaticallyAdjusted) {
            secondaryLoader.cancel();
            scrollPoller.cancel();
            scrollPoller.schedule(renderDelay);
        }

        scrollingWasProgrammaticallyAdjusted = false;
    }

    /**
     * Koliki ce biti offset za pretragu placeholdera
     * @param renderDistanceMultiplier
     */
    public void setRenderDistanceMultiplier(final double renderDistanceMultiplier) {
        distanceMultiplier = renderDistanceMultiplier;
    }

    public void setRenderDelay(final int renderDelay) {
        this.renderDelay = renderDelay;
    }

    /**
     * Trazim sve placeholdere za koje treba da se ucitaju komponente
     */
    protected void findAllThingsToFetchAndFetchThem() {
        findAllThingsToFetchAndFetchThem(pageHeight);
    }

    /**
     * Za svaki widget proveravam da li je trenmtno prikazan
     * @param distance
     * @return
     */
    private boolean findAllThingsToFetchAndFetchThem(final int distance) {

        boolean shownItemsHaveBeenFound = false;

        final Set<Widget> componentsToLoad = new HashSet<Widget>();
        for (int i = 0; i < panel.getWidgetCount(); i++) {
            final Widget child = panel.getWidget(i);
            final boolean isAPlaceholderWidget = child.getClass() == PlaceholderWidget.class;

            //            final boolean beingShown = isBeingShown(child, distance);                                    
            //NOVI METOD UZIMA TREBA MI VISINA KONTEJNERA
            final boolean beingShown = isBeingShown(VLazyLayout.this, child, distance);

            if (shownItemsHaveBeenFound && !beingShown) {
                /*
                 * suddenly the items aren't showing anymore - we're probably
                 * below the screen.
                 */
                break;
            }

            shownItemsHaveBeenFound = beingShown;

            //            if (beingShown) {
            //               debug("Vidi se element: " + child.getElement().getClassName());
            //            }

            if (isAPlaceholderWidget && beingShown) {
                componentsToLoad.add(child);
                //                debug("Jeste Placeholder i treba da se ucita novi: " + child.getElement().getClassName());
            }
        }

        if (!componentsToLoad.isEmpty()) {

            final List<Integer> idsToLoad = new ArrayList<Integer>();
            for (final Widget widgetPlaceholder : componentsToLoad) {
                idsToLoad.add(panel.getWidgetIndex(widgetPlaceholder));
            }

            if (fetcher != null) {
                debug("Fetching " + idsToLoad.size() + " components.");
                fetcher.fetchIndices(idsToLoad);
            } else {
                debug("LazyLayout has no fetcher!");
            }
        }

        return !componentsToLoad.isEmpty();
    }

    /**
     * Proveravam u DOM-u koji se widgeti trenutno vide
     * @param mainWidget To je u stvari ova komponenta
     * @param child
     * @param proximity
     * @return
     */
    private static boolean isBeingShown(final Widget mainWidget, final Widget child, final int proximity) {

        final Element element = child.getElement();

        /*
         * track the original element's position as we iterate through the DOM
         * tree
         */
        int originalTopAdjusted = 0;
        final int originalHeight = element.getOffsetHeight();
        int originalLeftAdjusted = 0;
        final int originalWidth = element.getOffsetWidth();

        com.google.gwt.dom.client.Element childElement = element;
        com.google.gwt.dom.client.Element parentElement = element.getParentElement();

        // What part of its canvas the parent shows, relative to its own
        // coordinates (0,0 is the top left corner)
        //            final int parentTop = parentElement.getScrollTop();            
        //VEROVATNO MORA DA SE UZME I SKROL POZICIJA ELEMENTA KOJI SKROLUJE
        final int parentTop = mainWidget.getElement().getScrollTop();

        //OVO JE VISINA CELOG KONTEJNERA - ONE JE MAKSIMALNA - MOZDA TREBA MERITI SAMO VISINU KOJA SE VIDI 
        //            final int parentBottom = parentTop + parentElement.getClientHeight();
        final int parentBottom = parentTop + mainWidget.getElement().getClientHeight();

        //        debug("Box: top: " + parentTop+ "px, btm: " + parentBottom + "px");

        final int parentLeft = parentElement.getScrollLeft();
        final int parentRight = parentLeft + parentElement.getClientWidth();

        /*
         * renderbox is the target box that is checked for visibility. If
         * the renderbox and parent's viewport don't overlap, it should not
         * be rendered. The renderbox is the child's position with an
         * adjusted margin.
         */
        final int renderBoxTop = childElement.getOffsetTop() - proximity;
        final int renderBoxBottom = childElement.getOffsetTop() + childElement.getOffsetHeight() + proximity;
        final int renderBoxLeft = childElement.getOffsetLeft() - proximity;
        final int renderBoxRight = childElement.getOffsetLeft() + childElement.getOffsetWidth() + proximity;

        if (!colliding2D(parentTop, parentRight, parentBottom, parentLeft, renderBoxTop, renderBoxRight,
                renderBoxBottom, renderBoxLeft)) {
            return false;
        }

        /*
         * see if the original component is visible from the parent. Move
         * the object around to correspond the relative changes in position.
         * The offset is always relative to the parent - not the canvas.
         */
        originalTopAdjusted += childElement.getOffsetTop() - childElement.getScrollTop();
        originalLeftAdjusted += childElement.getOffsetLeft() - childElement.getScrollLeft();
        if (!colliding2D(parentTop, parentRight, parentBottom, parentLeft, originalTopAdjusted - proximity,
                originalLeftAdjusted + originalWidth + proximity, originalTopAdjusted + originalHeight + proximity,
                originalLeftAdjusted - proximity)) {
            return false;
        }

        return true;
    }

    @Deprecated
    private static boolean isBeingShown(final Widget child, final int proximity) {

        final Element element = child.getElement();

        /*
         * track the original element's position as we iterate through the DOM
         * tree
         */
        int originalTopAdjusted = 0;
        final int originalHeight = element.getOffsetHeight();
        int originalLeftAdjusted = 0;
        final int originalWidth = element.getOffsetWidth();

        com.google.gwt.dom.client.Element childElement = element;
        com.google.gwt.dom.client.Element parentElement = element.getParentElement();

        //        while (parentElement != null) {

        // clientheight == the height as seen in browser
        // offsetheight == the DOM element's native height

        // What part of its canvas the parent shows, relative to its own
        // coordinates (0,0 is the top left corner)
        final int parentTop = parentElement.getScrollTop();

        //OVO JE VISINA CELOG KONTEJNERA - ONE JE MAKSIMALNA - MOZDA TREBA MERITI SAMO VISINU KOJA SE VIDI 
        //            final int parentBottom = parentTop + parentElement.getClientHeight();
        final int parentBottom = parentTop + parentElement.getClientHeight();

        final int parentLeft = parentElement.getScrollLeft();
        final int parentRight = parentLeft + parentElement.getClientWidth();

        /*
         * renderbox is the target box that is checked for visibility. If
         * the renderbox and parent's viewport don't overlap, it should not
         * be rendered. The renderbox is the child's position with an
         * adjusted margin.
         */
        final int renderBoxTop = childElement.getOffsetTop() - proximity;
        final int renderBoxBottom = childElement.getOffsetTop() + childElement.getOffsetHeight() + proximity;
        final int renderBoxLeft = childElement.getOffsetLeft() - proximity;
        final int renderBoxRight = childElement.getOffsetLeft() + childElement.getOffsetWidth() + proximity;

        if (!colliding2D(parentTop, parentRight, parentBottom, parentLeft, renderBoxTop, renderBoxRight,
                renderBoxBottom, renderBoxLeft)) {
            return false;
        }

        /*
         * see if the original component is visible from the parent. Move
         * the object around to correspond the relative changes in position.
         * The offset is always relative to the parent - not the canvas.
         */
        originalTopAdjusted += childElement.getOffsetTop() - childElement.getScrollTop();
        originalLeftAdjusted += childElement.getOffsetLeft() - childElement.getScrollLeft();
        if (!colliding2D(parentTop, parentRight, parentBottom, parentLeft, originalTopAdjusted - proximity,
                originalLeftAdjusted + originalWidth + proximity, originalTopAdjusted + originalHeight + proximity,
                originalLeftAdjusted - proximity)) {
            return false;
        }

        //            debug("Menjam Parent element - OVO NE BI TREBALO DA SE DOGODI!!!");
        //            childElement = parentElement;
        //            parentElement = childElement.getOffsetParent();
        //        }

        //TODO MISLIM DA OVO NE TREBA VISE
        // lastly, check the browser itself.
        //        final int parentTop = Window.getScrollTop();
        //        final int parentBottom = parentTop + Window.getClientHeight();
        //        final int parentLeft = Window.getScrollLeft();
        //        final int parentRight = parentLeft + Window.getClientWidth();
        //
        //        final int renderBoxTop = childElement.getOffsetTop() - proximity;
        //        final int renderBoxBottom = childElement.getOffsetTop() + childElement.getClientHeight() + proximity;
        //
        //        final int renderBoxLeft = childElement.getOffsetLeft() - proximity;
        //        final int renderBoxRight = childElement.getOffsetLeft() + childElement.getClientWidth() + proximity;
        //
        //        if (!colliding2D(parentTop, parentRight, parentBottom, parentLeft,
        //                renderBoxTop, renderBoxRight, renderBoxBottom, renderBoxLeft)) {
        //            return false;
        //        }
        //
        //        originalTopAdjusted += childElement.getOffsetTop();
        //        originalLeftAdjusted += childElement.getOffsetLeft();
        //        if (!colliding2D(parentTop, parentRight, parentBottom, parentLeft,
        //                originalTopAdjusted - proximity, originalLeftAdjusted
        //                        + originalWidth + proximity, originalTopAdjusted
        //                        + originalHeight + proximity, originalLeftAdjusted
        //                        - proximity)) {
        //            return false;
        //        }

        return true;
    }

    /**
     * Check whether a box overlaps (partially or completely) another.
     */
    private static boolean colliding2D(final int topA, final int rightA, final int bottomA, final int leftA,
            final int topB, final int rightB, final int bottomB, final int leftB) {

        final boolean verticalCollide = colliding1D(topA, bottomA, topB, bottomB);
        final boolean horizontalCollide = colliding1D(leftA, rightA, leftB, rightB);
        return verticalCollide && horizontalCollide;
    }

    /**
     * Check whether a line overlaps (partially or completely) another.
     */
    private static boolean colliding1D(final int startA, final int endA, final int startB, final int endB) {
        if (endA < startB) {
            return false;
        } else if (startA > endB) {
            return false;
        } else {
            return true;
        }
    }

    public void setFetcher(final ComponentFetcher componentFetcher) {
        fetcher = componentFetcher;
    }

    private void _replaceComponent(final Widget widget, final int i) {

        if (widget == null) {
            debug("LazyLayout: Widget for index " + i + " was null. Replacing with error indicator.");
            panel.remove(i);
            panel.insert(new HTML("<div style='background-color:red; color:white; font-weight: "
                    + "bold; border: 3px solid #f88; padding:5px'>" + "broken lazy layout component at index " + i
                    + "</div>"), i);
            return;
        }

        try {

            final Widget panelWidget = panel.getWidget(i);

            if (panelWidget.equals(widget)) {
                debug("Unnecessary placement command at index " + i);
                return;
            }

            panel.remove(i);
            panel.insert(widget, i);
        } catch (final IndexOutOfBoundsException e) {
            debug("Trying to replace a widget to a slot that doesn't exist. Index " + i);
        }
    }

    private void setScrollTop(final int topPx) {
        com.google.gwt.dom.client.Element parent = getElement();
        while (parent != null && parent.getScrollTop() <= 0) {
            parent = parent.getOffsetParent();
        }
        if (parent != null) {
            parent.setScrollTop(topPx);
            debug("setting scrolltop to " + topPx);
        } else {
            final int currentScrollLeft = Window.getScrollLeft();
            Window.scrollTo(currentScrollLeft, topPx);
            debug("setting scrolltop for window to " + topPx);
        }
        scrollingWasProgrammaticallyAdjusted = true;
    }

    /**
     * Gets the offset top of the closest component that's above the given
     * value.
     * 
     * @param topPixels
     *            top pixels into the layout
     * @return the position of the component above the given argument. 0 if it's
     *         above the first component. -1 if something went weirdly wrong (i
     *         guess no children available?)
     */
    private int getPreviousWidgetOffsetTop(final int topPixels) {
        int previousOffsetTop = 0;
        for (int i = 0; i < panel.getWidgetCount(); i++) {
            final Widget child = panel.getWidget(i);
            final int offsetTop = child.getElement().getOffsetTop();
            if (topPixels < previousOffsetTop) {
                return previousOffsetTop;
            } else {
                previousOffsetTop = offsetTop;
            }
        }
        return -1;
    }

    /**
     * Trenutna scroll pozicija
     * 
     * TODO Izbaciti ovo merenje PROZORA - WINDOW
     * 
     * @return
     */
    private int getCurrentScrollPos() {
        final com.google.gwt.dom.client.Element parent = getFirstScrolledElement();

        if (parent != null) {
            return parent.getScrollTop();
        } else {
            return Window.getScrollTop();
        }
    }

    /**
     * Ponalazi Prvi skroll element - OVO BI TREBALO DA BUDE ELEMENT WIDGETA
     * @return
     */
    private com.google.gwt.dom.client.Element getFirstScrolledElement() {

        //TODO Debagovati ovo
        //       com.google.gwt.dom.client.Element parent = getElement().getOffsetParent();
        com.google.gwt.dom.client.Element parent = getElement();
        while (parent != null && parent.getScrollTop() <= 0) {
            parent = parent.getOffsetParent();
        }
        return parent;
    }

    /**
     * Make sure to call {@link #updateScrollAdjustmentReference()}
     * appropriately (i.e. before you start changing the DOM) before calling
     * this method.
     */
    private void fixScrollbar() {
        if (referenceWidget != null) {
            if (referenceWidget.getParent() != panel) {
                debug("Scroll adjustment reference widget is no longer a child of me");
            }

            final int referenceOffsetTop = referenceWidget.getElement().getOffsetTop();
            debug("offset: " + referenceOffsetTop);
            setScrollTop(referenceOffsetTop + scrollOffsetToReferenceWidgetPx);

            referenceWidget = null;
            scrollOffsetToReferenceWidgetPx = 0;
        }
    }

    /**
     * This method re-evaluates the component the scroll adjustment should base
     * upon.
     * 
     * @see #fixScrollbar()
     */
    private void updateScrollAdjustmentReference() {
        final int currentScrollPos = getCurrentScrollPos();
        final Widget referenceWidget = getFirstNonPlaceholderWidgetInOrAfter(currentScrollPos);

        if (referenceWidget != null) {
            this.referenceWidget = referenceWidget;
            debug("is parent: " + (referenceWidget.getParent() == panel));
            scrollOffsetToReferenceWidgetPx = currentScrollPos - referenceWidget.getElement().getOffsetTop();
            debug("scroll offset: " + scrollOffsetToReferenceWidgetPx);
        } else {
            this.referenceWidget = null;
            scrollOffsetToReferenceWidgetPx = -1;
        }
    }

    /**
     * Gets the first non-placeholder widget in the current view.
     * 
     * @param topPixels
     * @return <code>null</code> if no suitable widget was found.
     */
    private Widget getFirstNonPlaceholderWidgetInOrAfter(final int topPixels) {
        // TODO: refactor. maek moar methods.

        for (int i = 0; i < panel.getWidgetCount(); i++) {
            final int childOffsetTop = panel.getWidget(i).getElement().getOffsetTop();

            if (topPixels < childOffsetTop) {
                /*
                 * we've reached the scrolled element's top. Let's search for
                 * the first non-placeholder widget
                 */

                if (i > 0) {
                    /*
                     * rewind one element, because that might be a
                     * non-placeholder widget, and we want to adjust to that.
                     */
                    i--;
                }

                for (; i < panel.getWidgetCount(); i++) {
                    final Widget candidateChild = panel.getWidget(i);
                    if (!(candidateChild instanceof PlaceholderWidget)) {
                        return candidateChild;
                    }

                    //                    if (candidateChild.getElement().getOffsetTop() > (topPixels + Window.getClientHeight())) {
                    if (candidateChild.getElement().getOffsetTop() > (topPixels + getElement().getClientHeight())) {
                        /*
                         * we've already checked for a screenful of elements.
                         * There's no non-placeholder widgets to adjust by.
                         * Screw it.
                         */
                        debug("Screen shows no non-placeholder elements.");
                        return null;
                    }
                }
            }
        }

        // lazylayout is off the screen completely.
        return null;
    }

    private static native double getPreciseHeight(Element element)
    /*-{
       if (typeof (element.getBoundingClientRect) == 'function') {
      var rect = element.getBoundingClientRect();
      return rect.bottom - rect.top;
       } else {
      return element.offsetHeight;
       }
    }-*/;

    private static double getPreciseHeight(final Widget widget) {
        return getPreciseHeight(widget.getElement());
    }

    public void refreshPageHeight() {
        if (getParent() != null) {
            pageHeight = getElement().getOffsetHeight();
            debug("Visina strane: " + pageHeight);
        } else {
            pageHeight = -1;
        }
        //        if (getParent() != null) {
        //            final int windowHeight = Window.getClientHeight();
        //            final int lazyLayoutHeight = getElement().getOffsetHeight();
        //            pageHeight = Math.min(windowHeight, lazyLayoutHeight);
        //            debug("Visina strane: " + pageHeight);
        //        } else {
        //            pageHeight = -1;
        //        }
    }

    private int getFetchDistancePx() {
        return (int) (pageHeight * distanceMultiplier);
    }

    public void replaceComponents(final int[] indices, final Widget[] widgets) {
        try {
            updateScrollAdjustmentReference();
            for (int i = 0; i < indices.length; i++) {
                _replaceComponent(widgets[i], indices[i]);
            }
            // renderQueue.add(indices, widgets);
        } catch (final IllegalArgumentException e) {
            debug(e.getMessage());
        }

        fixScrollbar();
    }
}