B4J Question Immutable Map

aeric

Expert
Licensed User
Longtime User
While developing Pakai v6, I faced an issue that I wish I don't want to talk about.
I tried numerous time but still failed to achieve a concept that I thought was easy.

I have forgotten that Map is always passed by reference.
I wished we have something like immutable map in B4J or B4X where I can declare a map, assign the value once and use many times which the value is not going to change.
Or we can pass by value to the map.

What I was trying to achieve is once I have generated a "static" html template (using MiniHtml), I want to keep the object inside a memory so I don't need to regenerate it again every time a request is made. I think this will boost the performance or make the app more efficient.

I wanted to use a global map in the server app Main module or maybe a thread safe map if it is necessary.
I like map because I can retrieve the value by a key very fast.

When I tried to assign the generated tag object into a map, the value can mutate and reference back to the map. This is not what I want.

For example, I have generated a html table row template look like this:
<tr><td></td><td></td><td></td></tr>

On first loop, I will pass the id = 1 and other values from the database query to this template object.
<tr><td>1</td><td>AAA</td><td>0001</td></tr>

On second loop, the template has already mutated with the updated value above. It is no longer the original template.

I tried to use KeyvalueStore.
However, the value is a non primitive type or more specificly the Tag class of MiniHtml. it will failed to deserialize from KVS.

I can convert the object to String first before storing to KVS but this will defeat the purpose. I have to parse the String and convert it back to Tag object. I think this will involve some CPU computation again. Why not just use something we have already processed?

So, I think immutable map is a good feature to add into B4J.

Or does any member have any alternative solution that is easy to implement?
 
Solution
B4X:
Private Sub Test8 ' Magma (edited)
    Dim trTemplate As Tag
    trTemplate = Tr.init
    Td.up(trTemplate)
    Td.up(trTemplate)
    Td.up(trTemplate)

    Dim Cache As Map = CreateMap("trTemplate": trTemplate)
        
    Dim Data As List = Array(CreateMap("name": "AAA"), CreateMap("name": "BBB"), CreateMap("name": "CCC"))

    Dim tbody1 As Tag = Tbody.init
    For Each item As Map In Data
        ' use DeepCloneTag <> Clone
        Dim trRow1 As Tag =  Cache.Get("trTemplate").As(Tag).DeepCloneTag(Cache.Get("trTemplate").As(Tag))
        trRow1.Child(1).text2(item.Get("name")) ' use text2 to be sure 
        LogColor(trRow1.Build, -16776961)
        trRow1.up(tbody1)
    Next
    
    LogColor(tbody1.Build, -65536)
End Sub
...

aeric

Expert
Licensed User
Longtime User
You mean the b4xlib on GitHub?
I attach here.
 

Attachments

  • MiniHtml.b4xlib
    37.3 KB · Views: 32
Upvote 0

Magma

Expert
Licensed User
Longtime User
You mean the b4xlib on GitHub?
I attach here.
B4X:
    Dim trTemplate As Tag
    trTemplate.Initialize("tr") 

    Dim trRow1 As Tag = trTemplate.Clone
    trRow1.add(Html.create("td").text("1"))
    trRow1.add(Html.create("td").text("AAA"))
    trRow1.add(Html.create("td").text("0001"))


    Dim trRow2 As Tag = trTemplate.Clone
    trRow1.add(Html.create("td").text("2"))
    trRow1.add(Html.create("td").text("bbb"))
    trRow1.add(Html.create("td").text("0002"))
    
    Log(trRow1.Build)
    Log(trRow2.Build)
 
Upvote 0

aeric

Expert
Licensed User
Longtime User
To explain what I want to do in Pakai,
Here are 2 Tests using MiniHtml (without running the Pakai server).
B4X:
Private Sub Test5
    ' Create a dummy data
    Dim rows As List
    rows.Initialize
    rows.Add(CreateMap("id": 1, "code": "T001", "name": "Teddy Bear"))
    rows.Add(CreateMap("id": 2, "code": "H001", "name": "Hammer"))
    rows.Add(CreateMap("id": 3, "code": "T002", "name": "Optimus Prime"))
    
    ' Create a tbody tag
    Dim tbody1 As Tag = Tbody.init

    For Each row As Map In rows
        Dim tr1 As Tag = CreateProductsRow(row)
        tr1.up(tbody1)
    Next
    
    Log(tbody1.Build) ' Print results to Logs
End Sub

CreateProductsRow sub is use to update a pre-generated template and return a html table row with live data from database:
B4X:
Private Sub CreateProductsRow (data As Map) As Tag
    Dim id As Int = data.Get("id")
    Dim code As String = data.Get("code")
    Dim name As String = data.Get("name")
    
    ' Generate the row tag and store for reuse
    If context.ContainsKey("row") = False Then
        context.Put("row", CreateProductsRowTemplate)
    End If

    ' Retrieve the tag
    Dim row1 As Tag = context.Get("row")
    
    ' Update tag with new data
    row1.Child(0).text2(id)
    row1.Child(1).text2(code)
    row1.Child(2).text2(name)
    
    Dim td3 As Tag = row1.Child(3)
    
    Dim anchor1 As Tag = td3.Child(0)
    anchor1.hxGet($"/api/products/edit/${id}"$)
    
    Dim anchor2 As Tag = td3.Child(1)
    anchor2.hxGet($"/api/products/delete/${id}"$)

    Return row1
End Sub

Where the template is generated and store in a global variable
B4X:
Private Sub CreateProductsRowTemplate As Tag
    Dim tr1 As Tag = Tr.init
    tr1.add(Td.cls("align-middle").sty("text-align: right"))
    tr1.add(Td.cls("align-middle"))
    tr1.add(Td.cls("align-middle"))
    Dim td3 As Tag = Td.cls("align-middle text-center px-1 py-1").up(tr1)

    Dim anchor1 As Tag = Anchor.cls("edit text-primary mx-2").up(td3)
    anchor1.hxGet("")
    anchor1.hxTarget("#modal-content")
    anchor1.hxTrigger("click")
    anchor1.data("bs-toggle", "modal")
    anchor1.data("bs-target", "#modal-container")
    anchor1.add(Icon.cls("bi bi-pencil"))
    anchor1.attr("title", "Edit")

    Dim anchor2 As Tag = Anchor.cls("delete text-danger mx-2").up(td3)
    anchor2.hxGet("")
    anchor2.hxTarget("#modal-content")
    anchor2.hxTrigger("click")
    anchor2.data("bs-toggle", "modal")
    anchor2.data("bs-target", "#modal-container")
    anchor2.add(Icon.cls("bi bi-trash3"))
    anchor2.attr("title", "Delete")
    
    Return tr1
End Sub

The current code in Pakai Server works 100%
but the app need to execute the code to regenerate every tags inside a row tag inside the loop.
For me this is not elegant, slow in performance or the code is a bit difficult to maintain.
B4X:
Private Sub Test6
    ' Create a dummy data
    Dim rows As List
    rows.Initialize
    rows.Add(CreateMap("id": 1, "code": "T001", "name": "Teddy Bear"))
    rows.Add(CreateMap("id": 2, "code": "H001", "name": "Hammer"))
    rows.Add(CreateMap("id": 3, "code": "T002", "name": "Optimus Prime"))
    
    ' Create a tbody tag
    Dim tbody1 As Tag = Tbody.init

    For Each data As Map In rows
        Dim id As Int = data.Get("id")
        Dim code As String = data.Get("code")
        Dim name As String = data.Get("name")
        
        Dim tr1 As Tag = Tr.init
        tr1.add(Td.cls("align-middle").sty("text-align: right")).text(id)
        tr1.add(Td.cls("align-middle")).text(code)
        tr1.add(Td.cls("align-middle")).text(name)
        Dim td3 As Tag = Td.cls("align-middle text-center px-1 py-1").up(tr1)
        
        Dim anchor1 As Tag = Anchor.cls("edit text-primary mx-2").up(td3)
        anchor1.hxGet($"/api/products/edit/${id}"$)
        anchor1.hxTarget("#modal-content")
        anchor1.hxTrigger("click")
        anchor1.data("bs-toggle", "modal")
        anchor1.data("bs-target", "#modal-container")
        anchor1.add(Icon.cls("bi bi-pencil"))
        anchor1.attr("title", "Edit")
        
        Dim anchor2 As Tag = Anchor.cls("delete text-danger mx-2").up(td3)
        anchor2.hxGet($"/api/products/delete/${id}"$)
        anchor2.hxTarget("#modal-content")
        anchor2.hxTrigger("click")
        anchor2.data("bs-toggle", "modal")
        anchor2.data("bs-target", "#modal-container")
        anchor2.add(Icon.cls("bi bi-trash3"))
        anchor2.attr("title", "Delete")

        tr1.up(tbody1)
    Next
    
    Log(tbody1.Build)
End Sub
 
Upvote 0

Magma

Expert
Licensed User
Longtime User
 
Upvote 0

aeric

Expert
Licensed User
Longtime User
Try Put the template tag inside a Map and then Get back the value.
 
Upvote 0

aeric

Expert
Licensed User
Longtime User
edit my code... to understand it...
Here is the edited version to let you understand what I mean:

B4X:
Private Sub Test8 ' Magma (edited)
    Dim trTemplate As Tag
    trTemplate = Tr.init
    Td.up(trTemplate)
    Td.up(trTemplate)
    Td.up(trTemplate)

    Dim Cache As Map = CreateMap("trTemplate": trTemplate)
        
    Dim Data As List = Array(CreateMap("name": "AAA"), CreateMap("name": "BBB"), CreateMap("name": "CCC"))

    Dim tbody1 As Tag = Tbody.init
    For Each item As Map In Data
        Dim trRow1 As Tag = Cache.Get("trTemplate").As(Tag).Clone
        trRow1.Child(1).text(item.Get("name"))
        LogColor(trRow1.Build, -16776961)
        trRow1.up(tbody1)
    Next
    
    LogColor(tbody1.Build, -65536)
End Sub

Logs:
<tr>
<td></td>
<td>AAA</td>
<td></td>
</tr>
<tr>
<td></td>
<td>AAABBB
</td>
<td></td>
</tr>
<tr>
<td></td>
<td>AAABBBCCC
</td>
<td></td>
</tr>


<tbody>
<tr>
<td></td>
<td>AAABBBCCC
</td>
<td></td>
</tr>
<tr>
<td></td>
<td>AAABBBCCC
</td>
<td></td>
</tr>
<tr>
<td></td>
<td>AAABBBCCC
</td>
<td></td>
</tr>
</tbody>


You can also replace .text() with .text2() to replace the previous text.
B4X:
trRow1.Child(1).text2(item.Get("name"))

Note: I always commit back the tests to the GitHub repo for anyone who want a quick test.
 
Upvote 0

Magma

Expert
Licensed User
Longtime User
B4X:
Private Sub Test8 ' Magma (edited)
    Dim trTemplate As Tag
    trTemplate = Tr.init
    Td.up(trTemplate)
    Td.up(trTemplate)
    Td.up(trTemplate)

    Dim Cache As Map = CreateMap("trTemplate": trTemplate)
        
    Dim Data As List = Array(CreateMap("name": "AAA"), CreateMap("name": "BBB"), CreateMap("name": "CCC"))

    Dim tbody1 As Tag = Tbody.init
    For Each item As Map In Data
        ' use DeepCloneTag <> Clone
        Dim trRow1 As Tag =  Cache.Get("trTemplate").As(Tag).DeepCloneTag(Cache.Get("trTemplate").As(Tag))
        trRow1.Child(1).text2(item.Get("name")) ' use text2 to be sure 
        LogColor(trRow1.Build, -16776961)
        trRow1.up(tbody1)
    Next
    
    LogColor(tbody1.Build, -65536)
End Sub

add this to Tag:
B4X:
Public Sub DeepCloneTag(originalTag As Tag) As Tag
    Dim newTag As Tag
    newTag.Initialize(originalTag.TagName)
    
    ' copy attributes
    Dim originalAttributes As Map = originalTag.Attributes
    For Each key As String In originalAttributes.Keys
        Dim value As String = originalAttributes.Get(key)
        newTag.Attributes.Put(key, value)
    Next
    
    ' copy styles
    Dim styleString As String = originalTag.StylesAsString
    If styleString.Length > 0 Then
        newTag.addStyle(styleString)
    End If
    
    ' copy classes
    Dim classString As String = originalTag.ClassesAsString
    If classString.Length > 0 Then
        newTag.addClass(classString)
    End If
    
    ' copy mode, flat, indentString
    newTag.Mode = originalTag.Mode
    newTag.Flat = originalTag.Flat
    
    ' Deep clone all children ?
    Dim originalChildren As List = originalTag.Children
    For i = 0 To originalChildren.Size - 1
        Dim Child1 As Object = originalChildren.Get(i)
        If Child1 Is Tag Then
            ' if tag: recursive deep clone
            Dim clonedChild As Tag = DeepCloneTag(Child1)
            newTag.add(clonedChild)
        Else
            ' if string just add..
            newTag.Children.Add(Child1)
        End If
    Next
    
    Return newTag
End Sub
 
Upvote 0
Solution

aeric

Expert
Licensed User
Longtime User
I marked above answer as solution but I would prefer to use a more straight forward way as something like in post #12.
I have commited this method in Pakai v6.10 development but it may be removed because this can make the template look more complicated to use.
 
Upvote 0
Cookies are required to use this site. You must accept them to continue using the site. Learn more…