In B4XPages we rely heavily on tags to identify UI elements. Yet tags are valuable for connecting data to the UI, too, and it is easy to feel we are wasting tags for identification.
There is a very simple and obvious way to use tags in a more flexible way, though. The following idea is not earth-shattering, just a demonstration of how we can solve the problem of the dearth of tags.
The starting point is:
1) In the UI designer(internal designer) we can only type strings as tag values.
2) In code we can assign any object to tags.
3) We don't have to assign values to or read values from tags directly, we can do it through simple Subs that reside in the Main module or in some "Global" code module.
4) This way we can dynamically create and assign Maps as tag values on the fly:
Assigning a value:
This is the basic stuff.
We can make other convenience methods with these.
For example, walk the node tree and find a node by name:
And then extend this to get a node tag value by node name and key:
Next, we can think about the case of dynamically(in code) created nodes, where we may not know how many nodes exist, and names would be a pain to manage.
Since we have Maps in Tags, we can assign a Name in the maps, and also an Index. So when you create an ImageView in code and add it to a panel, you can set its name to something like "imgWhatever", and its index to a value that grows in the code.
Then finding it is easy:
This will only work well if you do not remove nodes in code.
If you have similarly named nodes in a pane, and your code may remove some of them, then reindexing the remaining nodes would be a "pane in the..."
In that case just set a List in the parent pane's tag, maybe like
and manage the dynamically added/removed nodes from there.
It is easy to extend this kind of tag management. After trying it out, I now adopted it as the way to manage UI node tags. Your mileage may vary, this is just an idea.
There is a very simple and obvious way to use tags in a more flexible way, though. The following idea is not earth-shattering, just a demonstration of how we can solve the problem of the dearth of tags.
The starting point is:
1) In the UI designer(internal designer) we can only type strings as tag values.
2) In code we can assign any object to tags.
3) We don't have to assign values to or read values from tags directly, we can do it through simple Subs that reside in the Main module or in some "Global" code module.
4) This way we can dynamically create and assign Maps as tag values on the fly:
Getting tags from nodes:
Public Sub GetTag(ANode As Node, AKey As String) As Object
Private AMap As Map
If ANode.Tag = Null Then
AMap.Initialize
ANode.Tag = AMap
Return Null
Else If ANode.Tag Is Map Then
AMap = ANode.Tag
Return AMap.Get(AKey)
Else If ANode.Tag Is String Then 'Obviously, the tag was set in the designer, so it contains the name of the node.
'Create a map dynamically and move the node name into it with the key "Name".
AMap.Initialize
AMap.Put("Name", ANode.Tag)
Return AMap.Get(AKey)
Else
Log($"Get tag: Node had a tag ${ANode.Tag} already."$) 'It means we violated our own rule and accessed the tag directly.
AMap.Initialize
ANode.Tag = AMap
Return Null 'We lose the data here, but that is because our own code violates the rule that we should access node tags only through these Subs. Go and fix the code.
End If
End Sub
Assigning a value:
Setting Node Tags:
Public Sub SetTag(ANode As Node, AKey As String, AVal As Object)
Private AMap As Map
If ANode.Tag = Null Then
AMap.Initialize
AMap.Put(AKey, AVal)
ANode.Tag = AMap
Else If ANode.Tag Is Map Then
AMap = ANode.Tag
AMap.Put(AKey, AVal)
Else If ANode.Tag Is String Then
AMap.Initialize
AMap.Put("Name", ANode.Tag)
AMap.Put(AKey, AVal)
ANode.Tag = AMap
Else
Log($"Set tag: Node had a tag ${N.Tag} already."$)
AMap.Initialize
AMap.Put(AKey, AVal)
ANode.Tag = AMap
End If
End Sub
This is the basic stuff.
We can make other convenience methods with these.
For example, walk the node tree and find a node by name:
Node by Name:
Public Sub GetNode(APane As Pane, AName As String) As Node
Private ANodeName As String
For Each ANode As Node In APane.GetAllViewsRecursive
ANodeName = GetTag(ANode, "Name")
If (ANodeName = Null) Or (ANodeName = "") Then Continue
If ANodeName.EqualsIgnoreCase(AName) Then Return ANode
Next
Return Null
End Sub
And then extend this to get a node tag value by node name and key:
Tag Value by Node Name:
Public Sub GetNodeTag(APane As Pane, AName As String, AKey As String) As Object
Private ANode As Node = GetNode(APane, AName)
If ANode = Null Then Return Null
Return GetTag(ANode, AKey)
End Sub
Next, we can think about the case of dynamically(in code) created nodes, where we may not know how many nodes exist, and names would be a pain to manage.
Since we have Maps in Tags, we can assign a Name in the maps, and also an Index. So when you create an ImageView in code and add it to a panel, you can set its name to something like "imgWhatever", and its index to a value that grows in the code.
Then finding it is easy:
Indexed Nodes:
Public Sub GetIndexedNode(APane As Pane, AName As String, AnIndex As Int) As Node
For Each ANode As Node In APane.GetAllViewsRecursive
Private ANodeName As String = GetTag(ANode, "Name")
Private ANodeIndex As Int
If (ANodeName = Null) Or (ANodeName = "") Then Continue
If ANodeName.EqualsIgnoreCase(AName) Then
Private AnObj As Object = GetTag(ANode, "Index")
If (AnObj <> Null) And (AnObj Is Int) Then
ANodeIndex = AnObj
If AnIndex = ANodeIndex Then Return ANode
End If
End If
Next
Return Null
End Sub
This will only work well if you do not remove nodes in code.
If you have similarly named nodes in a pane, and your code may remove some of them, then reindexing the remaining nodes would be a "pane in the..."
In that case just set a List in the parent pane's tag, maybe like
List of Nodes:
Private Alist As List
AList.Initialize
SetTag(AParentPane, "Images", AList)
and manage the dynamically added/removed nodes from there.
It is easy to extend this kind of tag management. After trying it out, I now adopted it as the way to manage UI node tags. Your mileage may vary, this is just an idea.