# Dynamic component ordering

**URL:** <https://forum.inductiveautomation.com/t/dynamic-component-ordering/88534>\
**Category:** Ignition\
**Tags:** perspective\
**Created:** [April 8, 2024, 5:31pm UTC](https://forum.inductiveautomation.com/t/dynamic-component-ordering/88534 "2024-04-08T17:31:54Z")\
**Posts on this page:** 20\
**Page:** 1

<div class="post-metadata">

**Author:** ![Luke\_Reinke](https://avatars.discourse-cdn.com/v4/letter/l/d9b06d/32.png) [@Luke\_Reinke](https://forum.inductiveautomation.com/u/Luke_Reinke)\
**Post date:** [April 8, 2024, 5:31pm UTC](https://forum.inductiveautomation.com/t/dynamic-component-ordering/88534/1 "2024-04-08T17:31:54Z")

</div>

Does anyone know of a good way to make a dynamically ordered container? I am working on a maintenance/manual override view with a table listing out all relevant I/O connected to the PLC. Along the right side is a container (Currently a Flex Column) that is parent to Coordinate Container that is the manual override control.

 ![OverideView](https://us1.discourse-cdn.com/inductiveautomation/original/3X/2/1/2187e29fd54670565c9263096fc9c274d1d07785.png)

The toggle switch controls the selected output, and the check box assigns the container to the selected output.

The Override container block has been designed to allow for multiple instances to be added. I would like to always have one unassigned block visible. This isn't hard, however if for example there are four outputs being controlled, and five blocks visible. If the first block is deselected("Lock selection" is unchecked) all the remining blocks should be moved up and all new selections should be placed at the bottom of the coulomb. I think this can be done with coordinate containers, but I'm not sure if there is a better way.

---

<div class="post-metadata">

**Author:** ![paul-griffith](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/paul-griffith/32/37249_2.png) [@paul-griffith](https://forum.inductiveautomation.com/u/paul-griffith)\
**Post date:** [April 8, 2024, 5:58pm UTC](https://forum.inductiveautomation.com/t/dynamic-component-ordering/88534/2 "2024-04-08T17:58:16Z")

</div>

A flex repeater where you manually juggle the instances array?

If that isn't an option for some reason, a View Canvas...where you manually juggle the instances array?

---

<div class="post-metadata">

**Author:** ![Luke\_Reinke](https://avatars.discourse-cdn.com/v4/letter/l/d9b06d/32.png) [@Luke\_Reinke](https://forum.inductiveautomation.com/u/Luke_Reinke)\
**Post date:** [April 8, 2024, 6:15pm UTC](https://forum.inductiveautomation.com/t/dynamic-component-ordering/88534/3 "2024-04-08T18:15:39Z")

</div>

I'm fairly new to Ignition, how do I change an instance position in a flex repeater?

---

<div class="post-metadata">

**Author:** ![ryan.white](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/ryan.white/32/73193_2.png) [@ryan.white](https://forum.inductiveautomation.com/u/ryan.white)\
**Post date:** [April 8, 2024, 6:30pm UTC](https://forum.inductiveautomation.com/t/dynamic-component-ordering/88534/4 "2024-04-08T18:30:45Z")

</div>

Flex repeaters take a list of dictionaries for their instances, and the order of the items in the list is the displayed order. Move/change the item's position in the list and the change will be reflected in the flex repeater.

Something like this:

```auto
def swapArrayPositions(sourceArray, pos1, pos2):
	""" """
	sourceArray[pos1], sourceArray[pos2] = sourceArray[pos2], sourceArray[pos1]
	return sourceArray

```

should let you swap positions of two items in the list.

---

<div class="post-metadata">

**Author:** ![Luke\_Reinke](https://avatars.discourse-cdn.com/v4/letter/l/d9b06d/32.png) [@Luke\_Reinke](https://forum.inductiveautomation.com/u/Luke_Reinke)\
**Post date:** [April 8, 2024, 7:42pm UTC](https://forum.inductiveautomation.com/t/dynamic-component-ordering/88534/5 "2024-04-08T19:42:30Z")

</div>

Is it posable to dynamically create instances, or do they need to be defined? I'm thinking of having one instance defined, and add another when an output is selected.

---

<div class="post-metadata">

**Author:** ![ryan.white](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/ryan.white/32/73193_2.png) [@ryan.white](https://forum.inductiveautomation.com/u/ryan.white)\
**Post date:** [April 8, 2024, 7:59pm UTC](https://forum.inductiveautomation.com/t/dynamic-component-ordering/88534/6 "2024-04-08T19:59:19Z")

</div>

> [@Luke\_Reinke](#):
>
> Is it posable to dynamically create instances

Yes

> [@Luke\_Reinke](#):
>
> I'm thinking of having one instance defined, and add another when an output is selected.

This is completely possible. I have a case where once the input is completed in one instance, I add a new blank instance to allow more entry. You pretty much just need to add or remove items from the instance list via script, and the flex repeater will update accordingly.

Just have a value change script on the value from the selection that either adds or removes the extra instance from the list depending on the current value.

You could go even simpler and have both instances already defined, and bind the visibility of the second to the selection value.

---

<div class="post-metadata">

**Author:** ![Transistor](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/transistor/32/3380_2.png) [@Transistor](https://forum.inductiveautomation.com/u/Transistor)\
**Post date:** [April 8, 2024, 9:08pm UTC](https://forum.inductiveautomation.com/t/dynamic-component-ordering/88534/7 "2024-04-08T21:08:19Z")

</div>

![Hertz](https://us1.discourse-cdn.com/inductiveautomation/original/3X/d/4/d4ae9bd408b382072984e95b128047fea841a321.png)

Tip: **Hz** or **hertz** but never "hz"!  
SI units named after a person have their symbols capitalised and are lowercase when spelt out.

---

<div class="post-metadata">

**Author:** ![Luke\_Reinke](https://avatars.discourse-cdn.com/v4/letter/l/d9b06d/32.png) [@Luke\_Reinke](https://forum.inductiveautomation.com/u/Luke_Reinke)\
**Post date:** [April 9, 2024, 12:08pm UTC](https://forum.inductiveautomation.com/t/dynamic-component-ordering/88534/8 "2024-04-09T12:08:03Z")

</div>

> Tip: **Hz** or **hertz** but never "hz"!

I know, its a placeholder for motor drive speed settings. It will be RPM when I figure out what they are.

---

<div class="post-metadata">

**Author:** ![Luke\_Reinke](https://avatars.discourse-cdn.com/v4/letter/l/d9b06d/32.png) [@Luke\_Reinke](https://forum.inductiveautomation.com/u/Luke_Reinke)\
**Post date:** [June 5, 2024, 6:32pm UTC](https://forum.inductiveautomation.com/t/dynamic-component-ordering/88534/9 "2024-06-05T18:32:01Z")

</div>

I finally found time to work on this project again. The dynamic ordering is mostly working, it is using a message handler to update the FlexRepeter Instance structure.

```python
def onMessageReceived(self, payload):
	"""
	This method will be called when a message with the matching type code
	arrives at this component.

	Arguments:
		self: A reference to this component
		payload: The data object sent along with the message
	"""
	bInst = {"instanceStyle": {"classes": "","marginTop": 10},"instancePosition": {},"Table": {"activeRow": 0,"address": "", "discription": ""},"locked": False}
	inst = []
	length = 0
	try: 
		inst = list(self.props.instances)
		length = len(inst)-1
		if inst is None or length < 0:
			exception
# system.perspective.print("used existing structure "+str(length))
	except:
# system.perspective.print("created new structure")
		inst = []
		inst.append(bInst)
		length = len(inst)
	
	loop = True
	if length < 0 or inst == None:
		loop = False
	index = 0
	if loop:
# for y in range(0,1):
			for x in inst:
				# remove unused eliments
				if not x['locked'] and length > 0:
					if index != length:
						inst.pop(index)
						system.perspective.print("removed item at index: "+str(index))
				# check if last eliment is locked
				if index == length and x['locked']:
					inst.append(bInst)
					system.perspective.print("added Item")

				length = len(inst)-1	
				index += 1

	self.custom.index = length

	# update main display component
	self.props.instances = inst
	self.refreshBinding('props.instances')

```

Is there a better way to send the message then a "onMouseMove" event on the root container? I tried using a Gateway Timer Script, but that didn't work. It seams like a waste of server resources to run this script every time the mouse curser moves. I'm also not sure how an "onMouseMove" event works with a touch screen.

---

<div class="post-metadata">

**Author:** ![pturmel](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/pturmel/32/3295_2.png) [@pturmel](https://forum.inductiveautomation.com/u/pturmel)\
**Post date:** [June 5, 2024, 6:47pm UTC](https://forum.inductiveautomation.com/t/dynamic-component-ordering/88534/10 "2024-06-05T18:47:29Z")

</div>

> [@Luke\_Reinke](#):
>
> It seams like a waste of server resources to run this script every time the mouse curser moves.

All Perspective scripts run in the gateway server. If triggered by UI interactions like MouseMove, then yes, quite the waste. Consider including buttons in each row that trigger the desired motions upon mouse click. Much more reasonable traffic and scripting overhead.

---

<div class="post-metadata">

**Author:** ![Luke\_Reinke](https://avatars.discourse-cdn.com/v4/letter/l/d9b06d/32.png) [@Luke\_Reinke](https://forum.inductiveautomation.com/u/Luke_Reinke)\
**Post date:** [June 5, 2024, 6:58pm UTC](https://forum.inductiveautomation.com/t/dynamic-component-ordering/88534/11 "2024-06-05T18:58:07Z")

</div>

Does that work like the "onClick" or "onMouseDown" events? I tried both of these with not much succus. Ideally I would like it to send the message each time a check box changes state.

---

<div class="post-metadata">

**Author:** ![ryan.white](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/ryan.white/32/73193_2.png) [@ryan.white](https://forum.inductiveautomation.com/u/ryan.white)\
**Post date:** [June 5, 2024, 7:00pm UTC](https://forum.inductiveautomation.com/t/dynamic-component-ordering/88534/12 "2024-06-05T19:00:33Z")

</div>

Send a message using the `onActionPerformed` script of the checkbox.

Your loop can be reduced to

```python
def onMessageReceived(self, payload):
	"""
	This method will be called when a message with the matching type code
	arrives at this component.

	Arguments:
		self: A reference to this component
		payload: The data object sent along with the message
	"""
	bInst = {"instanceStyle": {"classes": "","marginTop": 10},"instancePosition": {},"Table": {"activeRow": 0,"address": "", "discription": ""},"locked": False}
	inst = []
	length = 0
	try:
		inst = list(self.props.instances)
		length = len(inst)-1
		if inst is None or length < 0:
			exception
	# system.perspective.print("used existing structure "+str(length))
	except:
		# system.perspective.print("created new structure")
		inst = []
		inst.append(bInst)
		length = len(inst)

	# Only update if there are items present and our instance exists
	if length > 0 and inst is not None:
		newInstanceList = [x for idx, x in enumerate(inst) if x['Locked'] and idx > 0]
		if newInstanceList[-1]['Locked']:
			newInstanceList.append(bInst)
	
	else:
		newInstanceList = inst
	
	self.custom.index = len(newInstanceList)

	# update main display component
	self.props.instances = newInstanceList
	self.refreshBinding('props.instances')

```

---

<div class="post-metadata">

**Author:** ![pascal.fragnoud](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/pascal.fragnoud/32/24728_2.png) [@pascal.fragnoud](https://forum.inductiveautomation.com/u/pascal.fragnoud)\
**Post date:** [June 5, 2024, 7:24pm UTC](https://forum.inductiveautomation.com/t/dynamic-component-ordering/88534/13 "2024-06-05T19:24:33Z")

</div>

I'm not sure what `exception` is supposed to be...  
Is it supposed to raise an exception so the `except` clause is executed ? That seems like a very convoluted way of doing things.  
Otherwise, what is `exception`, and what do you expect will trigger the `except` ?

Also, If that's the case, then the following condition will always be true, since if the list is empty, you're putting things in it. Or am I completely misunderstanding what's going on ?

Then, you're filtering your list, keeping only those where `locked` is `True`, and right after that you're checking if the last element in the list is locked. This will either be `True` if the list is not empty, or raise an exception because you're accessing a property of an object that doesn't exist.

I'd rewrite it, but frankly I'm quite lost: I have no clue what the whole thing should do.

Note: I based this comment on Ryan's code, which he says should be equivalent to your own code. The original code contains other things that you really shouldn't do, like popping things from the list you're looping through - that's a recipe for disaster.

If you can explain in more details what you're trying to accomplish, I'll give it a shot.

---

<div class="post-metadata">

**Author:** ![Luke\_Reinke](https://avatars.discourse-cdn.com/v4/letter/l/d9b06d/32.png) [@Luke\_Reinke](https://forum.inductiveautomation.com/u/Luke_Reinke)\
**Post date:** [June 5, 2024, 7:50pm UTC](https://forum.inductiveautomation.com/t/dynamic-component-ordering/88534/14 "2024-06-05T19:50:49Z")

</div>

I'm probably missing something simple, but sending the message from an actionPreformed event on the checkbox doesn't run the script. I tried a button in the same container as the FlexRepeter, and that didn't work ether.

---

<div class="post-metadata">

**Author:** ![Luke\_Reinke](https://avatars.discourse-cdn.com/v4/letter/l/d9b06d/32.png) [@Luke\_Reinke](https://forum.inductiveautomation.com/u/Luke_Reinke)\
**Post date:** [June 5, 2024, 7:58pm UTC](https://forum.inductiveautomation.com/t/dynamic-component-ordering/88534/15 "2024-06-05T19:58:32Z")

</div>

I think `exception` throws an exception that breaks out of the `try except` statement. As far as I know it's unique to Ignition.

I am trying to build a dynamic list of control tiles for a maintenance interface. If the "lock selection" checkbox is checked, I want a new tile to be created below. If the checkbox on any tile is unchecked, I want that tile to be removed.

Some of the code I posted may not be very well written as this project is well beyond anything I've done in Ignition.

---

<div class="post-metadata">

**Author:** ![ryan.white](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/ryan.white/32/73193_2.png) [@ryan.white](https://forum.inductiveautomation.com/u/ryan.white)\
**Post date:** [June 5, 2024, 7:59pm UTC](https://forum.inductiveautomation.com/t/dynamic-component-ordering/88534/16 "2024-06-05T19:59:23Z")

</div>

Did you use `system.perspective.sendMessage`? Did you have it send at the page level? Did you have the handler set to listen at the page level?

I send messages from templates to top level pages all the time. Its likely that your messages/message handlers were mis-configured.

> [@Luke\_Reinke](#):
>
> If the "lock selection" checkbox is checked, I want a new tile to be created below. If the checkbox on any tile is unchecked, I want that tile to be removed.

Consider changing your code approach entirely. Send a message from the instance that was toggled including its index in the payload. Pop only that index from the list and return the new list, or in the case of adding an instance, check that the instance is last in the list and append a new item to the list.

That's the method that I use. The examples below are from one of my interfaces where I can select an instance and delete it or add an instance.

> **Add Item Method**
>
> ```python
> def addMultiEntryInstance(sourceItems, DBHandler):
> """ """
> 
> # Attribute check on passed object to prevent calling a method that does not exist
> if not hasattr(DBHandler, "new"):
> logger.warn("Provided handler has no method 'new'")
> return sourceItems
> 
> sourceItems = shared.util.sanitizeIgnitionObject(sourceItems)
> blankItem = DBHandler.new()
> blankItem.update({"Instance": len(sourceItems)})
> 
> sourceItems.append(blankItem)
> 
> return sourceItems
> 
> ```

> **Remove Item Method**
>
> ```python
> def removeMultiEntryInstance(sourceItems, instanceIdx):
> """ """
> 
> # Bounds check for instance index
> if instanceIdx == -1 or instanceIdx >= len(sourceItems):
> return sourceItems
> 
> sourceItems = shared.util.sanitizeIgnitionObject(sourceItems)
> sourceItems.pop(instanceIdx)
> 
> if len(sourceItems) == 0:
> return []
> 
> # Rebuild instance index numbers
> for idx, item in enumerate(sourceItems):
> item.update({"Instance": idx})
> 
> return sourceItems
> 
> ```

---

<div class="post-metadata">

**Author:** ![Luke\_Reinke](https://avatars.discourse-cdn.com/v4/letter/l/d9b06d/32.png) [@Luke\_Reinke](https://forum.inductiveautomation.com/u/Luke_Reinke)\
**Post date:** [June 5, 2024, 8:08pm UTC](https://forum.inductiveautomation.com/t/dynamic-component-ordering/88534/17 "2024-06-05T20:08:59Z")

</div>

The Message Handler is set to listen at the Page level. I don't know how to check if the message is being sent at the Page level. This is the first time I have used Messages in Ignition.

```python
def runAction(self, event):
	"""
	This event is fired when the 'action' of the component occurs.

	Arguments:
		self: A reference to the component that is invoking this function.
		event: An empty event object.
	"""
	if not self.props.selected:
		self.getSibling("ToggleSwitch_1").props.selected = False
		self.props.selected = False
	
	messageType = 'updateList'
	system.perspective.sendMessage(messageType)
	system.perspective.print('msg sent')

```

Your example looks a lot simpler then what I came up with.

---

<div class="post-metadata">

**Author:** ![ryan.white](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/ryan.white/32/73193_2.png) [@ryan.white](https://forum.inductiveautomation.com/u/ryan.white)\
**Post date:** [June 5, 2024, 8:23pm UTC](https://forum.inductiveautomation.com/t/dynamic-component-ordering/88534/18 "2024-06-05T20:23:45Z")

</div>

The [manual](https://www.docs.inductiveautomation.com/docs/8.1/appendix/scripting-functions/system-perspective/system-perspective-sendMessage) specifies the additional parameters you can use when calling sendMessage. Also make sure the name of the message you are sending matches the name of the handler exactly (spaces/capitalization).

> [@Luke\_Reinke](#):
>
> `runAction`

~~Is this action on a button? Or is it a toggle?~~ This is on the checkbox, right? Is toggleSwitch\_1 supposed to match the state of this component?

Your action script would need to be changed to something like:

> **Action Script**
>
> ```python
> def runAction(self, event):
> """ """
> 	
> # Kept this in because I don't know what ToggleSwitch_1's purpose is
> if not self.props.selected:
> self.getSibling("ToggleSwitch_1").props.selected = False
> # self.props.selected = False #this is already False, why are we setting it False again?
> 
> payload = {
> 'selected': self.props.selected,
> 'instance': self.view.params.instance
> }
> 
> system.perspective.sendMessage("updateList", payload, scope="page")
> 
> return
> 
> ```

Your message handler would then look something like this:

> **Message Handler Script**
>
> ```python
> def onMessageReceived(self, payload):
> """ """
> 
> if not payload:
> return
> 
> selected = payload['selected']
> instanceIdx = payload['instance']
> bInst = {"instanceStyle": {"classes": "","marginTop": 10},"instancePosition": {},"Table": {"activeRow": 0,"address": "", "discription": ""},"locked": False}
> 
> # If checkbox was deselected, remove the instance from the list
> if not selected and instanceIdx != len(self.custom.instances) - 1:
> self.custom.instances = removeMultiEntryInstance(self.custom.instances, instanceIdx)
> 
> # If the instance is the last in the list, add another instance.
> elif selected and instanceIdx == len(self.custom.instances) - 1:
> instances = shared.util.sanitizeIgnitionObject(self.custom.instances)
> instances.append(bInst)
> instances[-1].update({"instance": len(instances) - 1})
> self.custom.instances = instances
> 
> return
> 
> ```

> **shared.util.sanitizeIgnitionObject**
>
> ```python
> def sanitizeIgnitionObject(element):
> """ """
> if hasattr(element, ' __iter__'):
> if hasattr(element, 'keys'):
> return dict((k, sanitizeIgnitionObject(element[k])) for k in element.keys())
> else:
> return list(sanitizeIgnitionObject(x) for x in element)
> return element
> 
> ```

---

<div class="post-metadata">

**Author:** ![Luke\_Reinke](https://avatars.discourse-cdn.com/v4/letter/l/d9b06d/32.png) [@Luke\_Reinke](https://forum.inductiveautomation.com/u/Luke_Reinke)\
**Post date:** [June 5, 2024, 8:33pm UTC](https://forum.inductiveautomation.com/t/dynamic-component-ordering/88534/19 "2024-06-05T20:33:22Z")

</div>

> [@ryan.white](#):
>
> Is this action on a button? Or is it a toggle? Is toggleSwitch\_1 supposed to match the state of this component?

that script is on the checkbox.

It is a toggle that is the control for the output that is selected from a table. It is set to False when the checkbox is unselected. Thinking about it now, it's probably not needed.

---

<div class="post-metadata">

**Author:** ![ryan.white](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/ryan.white/32/73193_2.png) [@ryan.white](https://forum.inductiveautomation.com/u/ryan.white)\
**Post date:** [June 5, 2024, 8:36pm UTC](https://forum.inductiveautomation.com/t/dynamic-component-ordering/88534/20 "2024-06-05T20:36:28Z")

</div>

I can see arguments for keeping that logic and removing it.

If you do keep it, bidirectionally bind its `selected` property to a custom property on the root container or view(name it something like `enableOutputSelection`).

Then you can change the line of code in the action script to `self.view.custom.enableOutputSelection = False` and it will work regardless of which container that toggle is located in.

Very helpful if you are still designing and moving stuff around.

[Next page](https://forum.inductiveautomation.com/t/dynamic-component-ordering/88534.md?page=2)
