Weird issues using Flex Repeater

I have a simple setup I've created many times before - it involves two levels of flex repeaters. This is needed for dynamic forms for this customer. The yellow box below is the root view (label, text field) and then the yellow box view is set as the path for the red box Row Flex Repeater. I then have another Column Flex Repeater (to create the Lot instances dynamically), which is the green box in the snip below.

Obviously, I want to be able to see the Text Field props.text from the main view (the one that contains the Form title and all the different types of materials). To do this, I set up an outbound view parameter called value in the yellow box. I then created a in/out view parameter called instances bound to the red box's Flex Repeater instances property. That binding is also bidirectional so that when I enter values into the Lot # and Quantity fields, I can see it in my Main view.

The weird behavior: see gif below
chrome_SpSaVG23Zi

The problem is that when I remove the instance at the appropriate index, it removes it and the underlying dataset shows the correct (green) flex repeater instances and its values. The problem is that the browser or something in my setup acts like the values in the text field are still filled out for some reason.

Even though you have the in/out view parameter, and you are deleting the second instance that has data, I don't think that Ignition deletes the way we think it does. I have seen this behavior in the designer when adding/removing instances manually in the property tab. For example, if you have 1 item and it has a binding...when you right-click to add another instance above the selected one, the binding doesn't move down...it stays on the first item. I think the "X" button should call a script to remove the binding at the topmost main view instances (green), not from the red. Propagating the instance change down only, not bi-directional.

Just to be clear, that is what's happening. When I hit the red X button to delete an instance, it sends a message to the green flex repeater and deletes it at the appropriate index (that I get automatically from the included Flex Repeater index property).

In the red box, I have a outbound view parameter called instances, and the Flex Repeater instances is bound to the view parameter bidirectionally. The reason why it's bidirectional is so that I can get the text field values that get entered into the red view from the green (topmost) repeater. If I uncheck bidirectional, I'll be able to create and delete instances fine, but then I don't have visibility into what the user actually entered (to save to a DB).

Yeap, understood. I implemented a dynamic form like this before with text fields as "cells" similar to Excel. Text changes in the field are bidirectional up to the red box...and I had to put a ChangeScript on the binding that updates the red box (making sure the change was from the user and not binding...there is a property for that) to modify the topmost master parameter containing a dictionary of my data (instead of doing bi-directional). That change caused a re-bind and refreshed cells if needed (wouldn't cause any weird flickering or refreshing of cells that had data).

1 Like

Yeah, that's my current thought as well - either use a change script or a message handler to update the values in the top-most data structure.

I always knew using output parameters was buggy, but I never thought it was this bad... I'll report back if I figure out an elegant solution.

I would only expect them to work with simple embedded views, not any repeaters or canvases.

I spent most of the day redoing it to use a change script on the lowermost Text Field's props.text property but it does the same [expletive]ing thing as the output parameters do, namely, that my top level Flex Repeater's instances property is correct (meaning there aren't values in those text fields) but the browser shows the previous index's values. See the gif above - same exact issue.

I did something similar once.
The requirement was this:
Have a dynamic form with multiple layers (because the fields were not all of the same type and may depend on previous selections), and have the possibility to load data into it for edition of existing things.

I used separate parameters for input and output.
Pass whatever needs to be displayed to the input, bind your repeaters instances to that, then bind the output to the instances and send that back to the calling view. Repeat for as many layers as needed.
This comes with added benefits: You can modify the form's data and still have the original data that was used to populate it. So you can use refreshBinding on the instances to cancel any change that was made, and you can compare the input and output to know what was modified.

1 Like

I recreated the functionality that you did to see if I could recreate the issue. I had the issue occur in my original design but I remade it and it is working see below:
Testing Flex Repeater

Only two views exist in this setup:
Main View which you see in the GIF.
FormField which is the form field.

Form Field Parameters:
image
Inside of the TextField bind the text property to:
view.params.value
Set Bidirectional to True

On the main view:
Flex Repeater with the Form Field view has only one instance by default with default parameters value is set to empty string (No Bindings).

The add button logic:

	self.view.custom.FieldCount = self.view.custom.FieldCount+1
	
	Instances = self.getSibling("FlexRepeater").props.instances
	
	Instances.append({
		    "instanceStyle": {
		        "classes": ""
		    },
		    "instancePosition": {},
		    "index": self.view.custom.FieldCount-1,
		    "label": "Form Field #{}".format(self.view.custom.FieldCount),
		    "value": ''
		})

The delete button also has logic to send a message which is stored on the main view this is the following logic:

	index = payload['index']
	
	Instances = self.getChild("FlexContainer").getChild("FlexRepeater").props.instances
	Filtered_Instances = [instance for instance in Instances if instance['index'] != index]
	system.perspective.print(Instances)
	system.perspective.print(Filtered_Instances)
	self.getChild("FlexContainer").getChild("FlexRepeater").props.instances = Filtered_Instances
	
	self.view.custom.FieldCount = self.view.custom.FieldCount-1

Wow - I'm flattered that you set up an entire example! Thank you for doing that.

The only difference I can see between my setup and yours is that I have another layer of Flex Container mixed in - namely, that I want to dynamically repeat rows AND columns. So, for example, where you have Form Field #4, I would need Form Field #4a, Form Field #4b, and so on.

The most frustrating part is that at my top level view, the instances object is valid, meaning when I drill down to look at the Row repeater instances (i.e. Form Field #4a, Form Field #4b, etc), I see the correct values in there. So if I have form fields and the second form field has data in both text boxes, when I delete the second row, the instances are exactly what they should be, but the browser/Text Field/Satan still holds onto the last value that was previously in that index.

I'm going to try @pascal.fragnoud's suggestion to use two different view parameters for input and output - maybe this will help.

Did you find a solution to this?

I spent too much time working on it - so it's now back in the backlog. In the grand scheme of things, its not the worst bug you could have as operators will usually not have multiple instances, but it could be confusing, especially since the instances.data objects don't match with what is displayed for those fields.