Dynamically set number of instances and value in flex repeater perspective

I am having trouble trying to figure out high level how to dynamically set the number of instances of a flex repeater and how to set the values within each instance. Lets say I just have a Label (text) and a Value (integer) I want to pass into each instance of the repeater. On the template of what I am using for the repeater I have two parameters coming in called Label and Value. I have a flex repeater. What is the next step? Would I go to a binding on the instances and then select something like an expression or an expression structure in order to set the number of instances and set the values for each instance. This is the part I am getting stuck on. In vision I know you can do something similar by using a custom method to write the script and then run the script from the component but you can’t do that in perspective so I am just wondering high level how to start on the right track.

I am also getting the Label property from a named query and this part will be dynamic based on how many rows of the table I am populating Label with. I can correctly bind the instances to this named query and return the correct number of instances along with the correct label per instance. Now I want to populate the value field per instance reading in tag values. How would I actually combine the two together? I cant get the value of the tag from a query so what am I to do?

1 Like

You'll want to bind against some sort of driving data (usually a query), and then include a transform. The transform should iterate over the results of the query and generate a list of objects, where each object contains the keys your instances will expect.

This is the general format you'll want to use:

instances = []
for row in value:  # where value is the passed-in value from your binding
    new_dict = {}
    new_dict['Value'] = row.value
    new_dict['Label'] = row.label
    instances.append(new_dict)
return instances

It's very likely you'll need to adjust the logic to properly iterate over a dataset, but your goal should be an array of objects where each object contains case-specific matches to the keys you have specified as params for the target View. The returned instances array is what will determine the count of instances.

This part right here actually seems backwards. If the "value" of the instance is derived from the value of the "Label", then why are you passing it in as a param? Have you considered ONLY passing the Label, and then allowing the View itself to determine the value of your "Value"?

1 Like

Well what I mean is there is a number associated with the label in the database. Then this number is used in the tag path to get the correct value. So do you mean if I can also pull this number along with the label value into each instance, then somehow use the number within the instance to use indirect tag addressing to get the tag value I need?

If they’re both coming from the same query, then yes.

If your query is something like select Label, Value from my_table, then you can iterate over that like I demoed in my previous post and send both values along. Then, inside your View which is being used in the Flex Repeater you can just bind a Label with an indirect tag binding where you supply the Value param as the interpolated value.

1 Like

Awesome! I get what you are saying. I thought I had to also include setting the tag values within the script. Actually if I wanted to rearrange the instances based on the value of the tag would I still want to create the dataset within the transform script. Then based on the tag value I can rearrange/change the order of the instances?

If you need to manage the order, then yes - you would need to do that within your transform script.

1 Like

I see.

So I have figured out how to create the instance that I want. I am reading in the tag values and have put them into their own datasets correctly. I now want to use those datasets to fill two of the four parameter instances. I am having two problems now. One is I cant seem to access one element within the dataset so each parameter instance ends up giving me the entire dataset for each instance. The second problem is while the dataset itself is showing just the value, what actually shows up on my screen is the entire tag including the value, quality, timestamp, etc.

To access the datasets I have tried using getValueAt(row.Number, 0) and that gives me an error. I also tried converting to a pydataset but that won’t work either.

For the issue of just getting the tag value I have it set up to just get the tag value using .value and that doesn’t work.

Here is the code that works but still gives me the two issues above:

	#create empty list to return to instances
	instances= []

	#create lists for reading in tag values with one read 
	temperaturetags = []
	operationtags = []
	
	for row in value:
		temperaturetags.append('[default]Machine {}/Temperature'.format(row.Number))
		operationtags.append('[default]Machine {}/Operation'.format(row.Number))

	temperatures = [row.value for row in system.tag.readAll(temperaturetags)]
	operations = [row.value for row in system.tag.readAll(operationtags)]
	
	#convert to pyDataSet
#	temperaturePy = system.dataset.toPyDataSet(temperatures)
#	operationPy = system.dataset.toPyDataSet(operations)
	
	#iterate through each row returned from query and populate the instance values
	for row in value:
		#create new empty dictionary
		new_dict = {}
		new_dict['Label'] = row.Label
		new_dict['Number'] = row.Number
		new_dict['Temperature'] = temperatures
		new_dict['Operation'] = operations
		instances.append(new_dict)
		 
	return instances

The flex repeater is showing something like this where the value on the left should be showing just a temperature value and the ‘null’ value should be showing the operation value. For the template I have the label on the left tied to view.params.temperature and the label on the right tied to view.params.operation

image

Here is an example of what each instance shows for operation and temperature (shows just the value but shows all values within the datasets)

image

image

Ok I think I figured it out. I can use row.Number to access the different elements but I had to subtract 1 from it. The dataset is 0-7 but the machine number index is 1-8.

The adjusted code temperature and operation is:

new_dict['Temperature'] = temperatures[row.Number-1]
new_dict['Operation'] = operations[row.Number-1]

Do you actually want the flex repeater rows to show static-in-time values of your tags at the point in time that you run the transform script (which is how your config will currently work) ? Or do you instead want these to update as a binding to the tags would?

Actually good point. I can still use the ‘Value’ number to give me the correct reference to the tag I want to pull. I did want to do some sorting based on the Operation parameter so I still wanted to include that in the transform as a way to get the value and do a system.dataset.sort.

I put this line in to sort:

sortedinstance = system.dataset.sort(instances, 3)

but I get an error telling me the first argument “instances” cant be coerced into a dataset. Would I have to convert instances into a dataset then complete the sort? Plus would I then have to convert the sorted dataset back into json? I need json format to work for instances.

You can sort your instances list of dicts by dict key using

Copied for ease:
The sorted() function takes a key= parameter

newlist = sorted(list_to_be_sorted, key=lambda k: k['name'])
4 Likes

Worked perfect, thanks!

I also tried and this worked also!

from operator import itemgetter

newinstance = sorted(instances, key=itemgetter('Operation'))
1 Like

I got good lesson from this question!
Please note that set ‘Return Format’ to ‘json’ if you want to transform your data.

1 Like

This looks like a good candidate for a list comprehension:

return [{'nTaskID': entry['nTaskID']} for entry in value]
3 Likes

How would we do it if we want these to update as a binding to the tags?

If the tags are fixed and there aren't too many of them, a structure binding.

If the list of tags is long or dynamic, have a list of paths stored somewhere, use a structure binding where one binding is on the paths list and the other one is an expression using now to trigger a refresh at fixed intervals.

think I understand the first part regarding the paths list, but using now to trigger a refresh at fixed intervals?

The bindings are re-evaluated when what they point to changes. If you only bind on your tags paths, then the binding would only be re-evaluated when that list changes.
You can force a refresh of the binding in several different ways, one of them is using the expression function now, which will evaluate on a fixed interval passed as a parameter, which in turn will trigger a re-evaluation of the binding.

I think. I never used that method.

https://docs.inductiveautomation.com/display/DOC81/now

1 Like

Im currently using a script query binding to dynamically populate my flex repeater instances (Similar to what @Behzad.Ebrahimi is doing).

My transform is the following:

def transform(self, value, quality, timestamp):
	instances = []
	for entry in value:
		instance = {}
		instance['WorkArea_Number'] = entry['WorkArea_Number']
		instance['WorkArea_Name'] = entry['WorkArea_Name']
		instance['Status'] = system.tag.read("[default]WorkAreas/WorkArea **X**/State.value")
		instances.append(instance)
	return instances

How can i pass the values i get for WorkArea_Number into the system.tag.read path where X is within the code?

Im trying to pass in a tag status value for each individual instance.

1 Like

Do you want the status to update when the tags update? If so, you don't want to pass in the value of the tag, but the path to the tag. Then you can use an indirect binding in the row View

(for statically reading tags, you should also be using system.tag.readBlocking, not just ... read)

1 Like