FlexRepeater, scroll to last instance

I've implemented a data entry that use FlexRepeater in order to present a set of rows.
The user can create new rows via an "Add Row" button.
It appears like this:
Here the user already created two rows.
If they add more rows they get regularly created but they go outside visibility and the scrollbar is displayed. Here I added another 3 rows.
I would like that each time a row is added the FlexRepeater scrolls down to the end of the list in order to ensure that that row is visible.
Manual scrolling here is impractical since the actual adding is done via a barcode scanner; that would require the user to put down the scanner after each insertion and this would reduce greatly the eficience of the User Interface.

This might help... Scroll to anchor on button onActionPerformed


I don't believe there's a good way to set a domId on a dynamic component like the Flex Repeater - maybe there is. You can definitely use a selector to style the last child, but whether it can be used as an anchor is a different story...

Ok the core idea seems to work, but there is still some missing piece to accomodate in order for this to work.

What i did

In the row-view, i had a parameter rowIndex that was the one used for assigning each instance a different identifier, infact the label shown in the picture has its text property bound to that parameter.

I just added the meta domId property to the same label and I bound it to the same view parameter.

I added a second button to test the function in isolation.
The OnActionPerformed for that button is simply:

lastInstance = len(self.getSibling(FlexRepeater).props.instances)-1|
system.perspective.navigate(url = "#" + str(lastInstance))|

Pressing "Navigate to last" scrolls to the last instance like a charm.

Unfortunately if I add the very same couple of lines of code in the "Add Row" button, it doesn't work: the scroll bar is not even moved.

I think that this is because navigation happens in the browser when all the page has been rendered.
Bindings on the other hand are resolved during the HTML-generation phase and thus before that HTML has been rendered on the client side. So the browser receives a navigation call towards an anchor that has not yet been rendered.
To overcome this i tried to: move into down-the line bindings, that is into the OnChange event of the text property of the label. Also here it doesn't work. Is there something like 'rendering complete event'?
Obviously as a last resort solution I could try to insert a delay. That would be a real unreliable approach though, so I'd strongly prefer avoiding such solutions.


Sorry about the rabbit hole... I tried everything you did plus using a message handler.

Would it be acceptable to add new items to the beginning of the list instead of the end, then there is no need to scroll.


When you attempt this from the add button, is the script being executed in separate event scripts or at the end of the same event script that adds the new instance to the flex repeater?

1 Like

I'm considering a couple of workarounds here, the first one being to have the rows in reverse order.
This requires discussion with the user though because their first request was to have them in chronological order. Obviously, this wouldn't be a solution but rather a change in requirements so that the problem doesn't exsist anymore.

An 'almost' working workaround: a dummy bottom-row with immutable domId
A workaround that I came up with is to have a last dummy line with same domId at all times (in my example is LASTONE). Measures are taken to make it invisible (height = 0). This row does always exist.
This happens to work but i find it too complicated, moreover it adds unwanted complexity.
A silghtly better solution is to actually move to last line a fixed identifier.
This is basically a variation on this solution but overcomes a couple of issues that are having a dummy row possibly triggering unwanted logic and having to hide it. Moving the identifier only happens in the instance generation method and in the label itself and it proved to work.

Unfortunately both of the proposed workaround work well in 'simple cases' but show a faulty behavior when the row are more complex. See the bottomline.
The changes are the following; i inserted an initial instance (directly in the designer) with rowIndex= null.

In the domId binding i added the following transformation (script):

if value is None:	
	return "LASTONE"	
return value

Now the null rowIndex line has the LASTONE DOM identifier.
It has to always be the last one so the Add Row button OnActionPerformed has to be changed.
The line:


must be changed to:

self.getSibling("FlexRepeater").props.instances.insert(newInstanceIndex - 1, newInstance)

Additionally, I bound the height of the row view (here I used a coordinate container, so it has an height) to the rowIndex property so that the height is 0 when the row is the dummy-lastline. That is:

return 0 if value is None else 49

In both buttons the call for scrolling obviously is:

system.perspective.navigate(url = "#LASTONE")

There is an barely perceivable flickering when adding a row, but it does its job.
I leave the question open for a while to attract more proper solutions.

More tests have been done on both the solutions (bottom row and change of domId) and in all cases they showed faulty/unreliable behavior as the number of controls in each row grew; the scrolling did not happened all the times and in some cases was partial.
This shows one more the 'tricky' nature of these workarounds and the lack of a proper solution to this problem that should be considered by Inductive Automation team.

I tried to explore the option you mentioned.
Initially the navigation action happened in the same script that adds the row.
Now I added a second script and I moved the navigation code here, as in the following image.

It doesn't make any difference.

All of these workarounds and what not make me wonder,

Would a unique combination of the table domId + rowIndex set as the domId of each row in the table enable this sort of functionality easier? That way your embedded view doesn’t need to be smart enough to figure all this out on its own. This could easily be applied on the rowGroup that wraps the embedded view so that your pre-existing domIds would all work as well.