Auto Scroll - Automatically add content as the user scrolls the mouse wheel

https://anvil.works/build#clone:7R3RNR37JTENAM7A=TBL2A62OK3XBWOWAYT3KWHKY

Adding items to a repeating panel requires the assignment of a new list to its items, which clears its content and regenerates everything, including what was already there.

Using a linear panel instead of a repeating panel allows to add a few components at a time. Each component will be rendered as it is added.

This AutoScroll class adds a listener to the mousewheel event of the linear panel and calls a function that adds a few rows when the scroll bar is close to the bottom.

    def form_show(self, **event_args):
        AutoScroll(self.linear_panel, self.load_next_page)

All you need to do is creating an instance of the class. You don’t need to store it in a variable, because it will store itself in the component’s tag. (Actually it does store itself in the tag, but it would work even if it didn’t, the magics of closures! Why does it store itself in there then? I don’t really know, I thought it would be useful one day).

The object must be created in the form_show event, after the html elements are created.

The arguments are:

class AutoScroll:
  def __init__(
    self, 
    component_with_scrollbar,  # the linear panel (or any other container)
    loader_function,           # a function that adds a few rows to the linear
                               # panel
    *,                         # (the following arguments must be named)
    scrollbar_load_threshold=300,  # call the loader_function when the scrollbar 
                               # is this far from the bottom of the page
    start_loading=True,        # call the loader_function immediately, without
                               # waiting for the mousewheel event
    debugging=False            # print what's going on
  ):

You can start_loading=False if you want to pre-load the first page.

You can increase the scrollbar_load_threshold if you want it to be more aggressive and call the loader_function sooner.


I was not able to manage other events. For example it doesn’t work when you scroll down by dragging the scrollbar, the way you did before the mouse wheel was invented. If anyone out there can crack the code for this, please let me know and I will update the app.

I really don’t like how it’s looking for the scrollbar, there has to be a better way. My old apps know where the scrollbar is because they have the control over the html and the css, but here I wanted to do everything contained in one class, so, yeah, let me know if you can figure out a better way.


I just created this module with a little copy and paste from old apps that do something similar. In the old apps I was using javascript, css, customized html page template… it was a mess. So I tried to create one class that does the job in a cleaner way, and… well, it works, but I’m not really happy.

Using custom html and javascript and css it was possible to target a specific DOM element, while using Anvil + Anvil Extras I had to do some acrobatics to find the scrollbar and get its position. So I’m not really happy, but it works on the simple test form it comes with. It may break in a more complex form, I haven’t done much testing. Try and let me know how it goes.

10 Likes

That’s great work! I tested it out and it works nicely.

As for manually scrolling, the reason why it wasn’t working is that there is no scrollbar in the component you are trying to set it to. The scrollbar is actually added to

  • nav-holder class for devices having less width than 998px
  • content class for devices having greater width

So here, we just set the scroll event for both these classes. This also means that it no longer relies on Anvil Extras (sorry anvilistas) and can exist independently.

https://anvil.works/build#clone:46BAVTT2IFLZ3TN7=SB46B635LS24XQJOOLGQTECR

But I have a feeling that I might have ended up breaking some of your untouched work so just have a look :grinning:

5 Likes

Good stuiff! We need this built into Anvil! :grinning:

That is impressive! I thought we would have to use JS script to do that, good to learn.

Hi @divyeshlakhotia,

Thanks for figuring out a cleaner way to find the elements with the scrollbar. I integrated your changes to my app and removed the ugly code that was looking for the element with the scrollbar.

After the cleanup I realized that there is no need to pass the linear panel as an argument to the class constructor, so I cleaned up that too. In my mind I was hoping to use this module as a generic module for multiple scrollable components in the same form, but, seriously, I’ve never seen that happening. The they it will happen I will update the component. For now I will use it to manage the one scroll bar on the form.

Now it looks much nicer, but… there is still some new ugly code.

Your jQuery returns 2 elements and I couldn’t figure out a way to get the app to work both in split mode and in separate tab. After some trial and error I found out that it works by getting some minimum and some maximum values.

That code is ugly because I don’t understand why I need to get the maximum or the minimum values and because I have the feeling that there is a better way to get the maximum and the minimum with jQuery.

A jquery object is a bit like an array of javascript dom nodes
so iterating over the jquery object will give you dom nodes

You could replace the methods that are working on jquery objects

    def max_scroll_top(self, jq_object):
        return max(jq_object.slice(n).scrollTop() for n in range(len(jq_object)))

with methods that work on the javascript dom nodes instead

    def max_scroll_top(self, jq_object):
        return max(dom_node.scrollTop for dom_node in jq_object)

And then since jq_object is always self.jq_elements_with_scrollbar
you could just remove the second argument to the function

note:
jquery_object.height() -> dom_node.clientHeight

1 Like

Another workaround might be to detect when the last component scrolls into view and use it to load more rows. I have yet to try it out but there appear to be some options like morr/jquery.appear or IntersectionObserver - Web APIs | MDN (mozilla.org)

1 Like