Announcement

Collapse
No announcement yet.
X
  • Filter
  • Time
Clear All
new posts

    How to preserve VLayout scroll position when content changes

    I’m using SmartGWT 5.1-p20170201 on Firefox 26.0 on Windows

    In my application I do the following:
    1. Inside of a scrolling layout (setOverflow(Overflow.AUTO)) I add content by adding a layout and then adding members to that layout
    2. I scroll to the bottom of the scrolling layout
    3. Through user interaction I replace the member added above with a new member that is constructed in the same way.

    This results in the scroll position being moved from the bottom to the top.

    My desired behavior is that after I remove and replace a member the scroll position is preserved.

    How can I do this?

    Is there a way to disable this automatic scrolling to the top?

    Is there a way to save and restore the scroll position?

    Here is the sample code which demonstrates this behavior:

    Code:
    [B]public[/B] [B]class[/B] ScrollPositionEntry [B]extends[/B] VLayout [B]implements[/B] EntryPoint {
        [B]private[/B] VLayout scrollingLayout;
        [B]private[/B] VLayout container;
     
        @Override
        [B]public[/B] [B]void[/B] onModuleLoad() {
     
            setDefaultLayoutAlign(Alignment.[B][I]CENTER[/I][/B]);
     
            scrollingLayout = scrollingLayout();
            addMember(scrollingLayout);
     
            container = container();
            scrollingLayout.addMember(container);
            addContentToContainer();
     
            addMember([B]new[/B] Button() {
                {
                    setTitle("Replace");
                    addClickHandler(event -> {
                        Canvas toReplace = scrollingLayout.getMember(0);
                        toReplace.destroy();
                        container = container();
                        scrollingLayout.addMember(container, 0);
                        addContentToContainer();
                    });
                }
            });
            show();
        }
     
        [B]private[/B] VLayout container() {
            [B]return[/B] [B]new[/B] VLayout() {
                {
                    setWidth(300);
                    setAutoHeight();
                    setBorder("6px solid red");
                }
            };
        }
     
        [B]private[/B] [B]void[/B] addContentToContainer() {
            Stream.[I]of[/I]("red", "green", "orange", "yellow", "purple")
                  .forEach(color -> container.addMember([B]new[/B] HLayout() {
                      {
                          setBorder("6px solid " + color);
                          setWidth100();
                          setHeight(100);
                      }
                  }));
        }
     
        [B]private[/B] VLayout scrollingLayout() {
            [B]return[/B] [B]new[/B] VLayout() {
                {
                    setBorder("6px solid blue");
                    setDefaultLayoutAlign(Alignment.[B][I]CENTER[/I][/B]);
                    setHeight(400);
                    setMargin(10);
                    setMembersMargin(10);
                    setOverflow(Overflow.[B][I]AUTO[/I][/B]);
                    setVisible([B]true[/B]);
                    setWidth(400);
                }
            };
        }
    }
    To show the behavior do the following:
    1. Run the application
    2. Scroll to the bottom of the scrolling layout
    3. Click the “Replace” button
    4. This will result in the scroll position to be set at the top

    #2
    The automatic scrolling to the top is unavoidable in this case. By removing members from the layout, the scrollable size changes (making the old scroll position invalid, and automatically scrolling to a valid position), and the component has no way of knowing, if content is subsequently added to it, that the desired effect is to scroll back down to the previous scroll position.
    However this should be easy to resolve at the application level. A couple of suggestions.
    If your content will be the same size, you can simply call getScrollTop() on the widget in question prior to changing its content, remember that value and then call scrollTo(null, scrollTop); after the change.
    If you instead want to keep a specific widget in view but the size of the new content above the widget may vary, you could remember its position within the parent's viewport (viewportOffset = widget.getTop() - parentWidget.getScrollTop()), and reset to the same offset after the content changes (parent.scrollTo(null, widget.getTop()+viewportOffset);)

    Hope this helps!
    Regards
    Isomorphic Software

    Comment


      #3
      Originally posted by Isomorphic View Post
      If your content will be the same size, you can simply call getScrollTop() on the widget in question prior to changing its content, remember that value and then call scrollTo(null, scrollTop); after the change.
      Using getScrollTop and scrollTo works only if the scrollTo is not called in the button click handler.

      It works if the scrollTo is delayed slightly (e.g. 10 milliseconds).

      My guess is this is because the browser/GWT will only perform the scrollTo if the new contents have been drawn. But an explanation from Isomorphic would be better than a guess from me.

      This approach has an unfortunate side effect of causing a flicker.

      Please let me know if you have an alternative approach where I can avoid the flicker.

      Here is the updated code showing the use of getScrollTop and scrollTo called in a timer.

      Code:
      [B]public[/B] [B]class[/B] ScrollPositionEntry [B]extends[/B] VLayout [B]implements[/B] EntryPoint {
          [B]private[/B] VLayout scrollingLayout;
          [B]private[/B] VLayout container;
       
          @Override
          [B]public[/B] [B]void[/B] onModuleLoad() {
       
              setDefaultLayoutAlign(Alignment.[B][I]CENTER[/I][/B]);
       
              scrollingLayout = scrollingLayout();
              addMember(scrollingLayout);
       
              container = container();
              scrollingLayout.addMember(container);
              addContentToContainer();
       
              addMember([B]new[/B] Button() {
                  {
                      setTitle("Replace");
                      addClickHandler(event -> {
                          [B]int[/B] top = scrollingLayout.getScrollTop();
                          Canvas toReplace = scrollingLayout.getMember(0);
                          container = container();
                          scrollingLayout.addMember(container, 0);
                          addContentToContainer();
                          toReplace.destroy();
                          [B]new[/B] Timer() {
                              @Override
                              [B]public[/B] [B]void[/B] run() {
                                  scrollingLayout.scrollTo([B]null[/B], top);
                              }
                          }.schedule(10);
                      });
                  }
              });
              show();
          }
       
          [B]private[/B] VLayout container() {
              [B]return[/B] [B]new[/B] VLayout() {
                  {
                      setWidth(300);
                      setAutoHeight();
                      setBorder("6px solid red");
                  }
              };
          }
       
          [B]private[/B] [B]void[/B] addContentToContainer() {
              Stream.[I]of[/I]("red", "green", "orange", "yellow", "purple")
                    .forEach(color -> container.addMember([B]new[/B] HLayout() {
                        {
                            setBorder("6px solid " + color);
                            setWidth100();
                            setHeight(100);
                        }
                    }));
          }
       
          [B]private[/B] VLayout scrollingLayout() {
              [B]return[/B] [B]new[/B] VLayout() {
                  {
                      setBorder("6px solid blue");
                      setDefaultLayoutAlign(Alignment.[B][I]CENTER[/I][/B]);
                      setHeight(400);
                      setMargin(10);
                      setMembersMargin(10);
                      setOverflow(Overflow.[B][I]AUTO[/I][/B]);
                      setVisible([B]true[/B]);
                      setWidth(400);
                  }
              };
          }
      }

      Comment


        #4
        Hi, any update on what event handler / override can be used to avoid flicker?

        The direct line of thought, if the scroll can not be applied immediately (in most cases SmartClient just ignores it), provide a scroll API that will trigger reflow and will apply pending scroll at the end of reflow.
        Last edited by michaelplavnik; 17 Mar 2021, 14:05.

        Comment


          #5
          We're not really clear on what the previous user was posting about or why he tried to use a timer.

          Use replaceMember() if something is the same size, and no scrolling should occur.

          If you change members around and want to scroll without flicker, just call reflowNow() on the Layout and then scroll to where you want.

          Comment

          Working...
          X