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
See #9!
Make the master map into a class to ease use.

B4X:
'ImMap Class'
Sub Class_Globals
    Private fx As JFX
    Private ThisMap As Map
    Private defaultObject As Object
End Sub

'Initializes the object. You can add parameters to this method if needed.
Public Sub Initialize(default As Object) As ImMap
    ThisMap.initialize
    defaultObject = default
    Return Me
End Sub

Public Sub Put(mapId As String, mp As Map)
    Dim tempMap As Map
    tempMap.Initialize
    For Each key As Object In mp.Keys
        tempMap.Put(key, mp.Get(key))
    Next
    ThisMap.put(mapId, tempMap)
End Sub

Public Sub Get(mapId As String, key As Object) As Object
    Dim tempMap As Map = ThisMap.get(mapId)
    Return tempMap.GetDefault(key, defaultObject)
End Sub

The use as this

B4X:
    Private immp As ImMap
    immp.Initialize("???")


    immp.Put("Context1", CreateMap("A": 1000))
    immp.Put("Context2", CreateMap("A": 3000, "C": 5000))
   
    Log(immp.Get("Context2", "A"))
I am not sure this will work with my custom class.
I think MiniHtml Tag is a bit complex. Don't get fooled by the name "Mini" and version 0.xx. 🙂
 
Upvote 0

aeric

Expert
Licensed User
Longtime User
Let me try to use random access memory to see if it works for serializing and deserializing the object.
Time to bed. 3:30am now.
 
Upvote 0

Magma

Expert
Licensed User
Longtime User
Let me try to use random access memory to see if it works for serializing and deserializing the object.
Time to bed. 3:30am now.
try to use minihtml but taking the following (what other libs need ?)
1763062388890.png
 
Upvote 0

William Lancee

Well-Known Member
Licensed User
Longtime User
I think it is a bit complex
That's why in a class access would be simpler.

I have experimented quite a bit with MiniHtml so I am familiar with the methods. It is sophisticated in class usage.

Note that the map_object passed in the Put method of imMap is copied to an anonymous map object, so the original is not modified.
I have tested the code and it works as posted.
 
Upvote 0

William Lancee

Well-Known Member
Licensed User
Longtime User
Here is a modified version that works with B4XSerializer. Take a look after you get some sleep :)
B4X:
Private Sub B4XPage_Created (Root1 As B4XView)
    Root = Root1
    Private immp As ImMap
    immp.Initialize("???")        'default value

    immp.Put("Context1", CreateMap("A": 1000))
    immp.Put("Context2", CreateMap("A": 3000, "C": 5000))
    
    Log(immp.Get("Context2", "A"))
    
    Dim ser As B4XSerializator
    Dim bytes() As Byte = ser.ConvertObjectToBytes(immp.AsMap)
    
    Dim immpcopy As ImMap
    immpcopy.Initialize("???")
    immpcopy.AsMap = ser.ConvertBytesToObject(bytes)   
    Log(immpcopy.Get("Context2", "A"))
        
End Sub

B4X:
Sub Class_Globals
    Private fx As JFX
    Public AsMap As Map
    Private defaultObject As Object
End Sub

'Initializes the object. You can add parameters to this method if needed.
Public Sub Initialize(default As Object) As ImMap
    AsMap.initialize
    defaultObject = default
    Return Me
End Sub

Public Sub Put(mapId As String, mp As Map)
    Dim tempMap As Map
    tempMap.Initialize
    For Each key As Object In mp.Keys
        tempMap.Put(key, mp.Get(key))
    Next
    AsMap.put(mapId, tempMap)
End Sub

Public Sub Get(mapId As String, key As Object) As Object
    Dim tempMap As Map = AsMap.get(mapId)
    Return tempMap.GetDefault(key, defaultObject)
End Sub
 
Upvote 0

aeric

Expert
Licensed User
Longtime User
Check B4XCollection.CopyOnWriteMap. It creates a new internal map whenever the map mutates. References to the old internal map are still valid.
Yes, I tried it.
B4X:
Private Sub start
    Dim CopyMap As CopyOnWriteMap
    CopyMap.Initialize(CreateMap(1: "a", 2: "b"))
    DoSomethingWithMap("v1", CopyMap.GetMap)
    CopyMap.Put(3, "c")
    DoSomethingWithMap("v2", CopyMap.GetMap)
End Sub
I didn't see how it works.

I tried again just now.
I think it works now when I pass a map declared outside as an variable instead of using CreateMap.
B4X:
Sub Process_Globals
    Private original As Map = CreateMap(1: "a", 2: "b")
End Sub

Private Sub start
    Dim CopyMap As CopyOnWriteMap
    CopyMap.Initialize(original)
    DoSomethingWithMap("v1", CopyMap.GetMap)
    CopyMap.Put(3, "c")
    DoSomethingWithMap("v2", CopyMap.GetMap)
End Sub

Private Sub DoSomethingWithMap (Name As String, m1 As Map)
    For i = 1 To 3
        Log($"Step ${i}"$)
        Log($"before ${Name}: ${m1}"$)
        If i = 2 Then m1.Put(2, "x")
        If i = 3 Then m1.Remove(2)
        Log($"after ${Name}: ${m1}"$)
        Log(" ")
        Sleep(1000)
    Next
    Log("original: " & original)
End Sub
 
Upvote 0

Magma

Expert
Licensed User
Longtime User
 
Upvote 0

aeric

Expert
Licensed User
Longtime User
The problem is here:
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")
    Dim price As Double = data.Get("price")
    Dim category As String = data.Get("category")

    If App.ctx.ContainsKey("/table/row") = False Then
        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)
        tr1.add(Td.cls("align-middle"))'.text(category)
        tr1.add(Td.cls("align-middle").sty("text-align: right"))'.text(NumberFormat2(price, 1, 2, 2, True))
        Dim td6 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(td6)
        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(td6)
        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")
        
        ' Store for reuse
        App.ctx.Put("/table/row", tr1)
    End If
    
    Dim ctx As CopyOnWriteMap
    ctx.Initialize(App.ctx)
    Dim row1 As Tag = ctx.GetMap.Get("/table/row")
    Log($"temp: ${row1.Child(0).innerText} | ${row1.Child(1).innerText} | ${row1.Child(2).innerText}"$)
    
    ' Update tag with new data
    row1.Child(0).text2(id)
    row1.Child(1).text2(code)
    row1.Child(2).text2(name)
    row1.Child(3).text2(category)
    row1.Child(4).text2(NumberFormat2(price, 1, 2, 2, True))
    Dim td6 As Tag = row1.Child(5)
    Dim anchor1 As Tag = td6.Child(0)
    anchor1.hxGet($"/api/products/edit/${id}"$)
    Dim anchor2 As Tag = td6.Child(1)
    anchor2.hxGet($"/api/products/delete/${id}"$)
    
    Log($"row: ${row1.Child(0).innerText} | ${row1.Child(1).innerText} | ${row1.Child(2).innerText}"$)
    Return row1
End Sub

Maybe I need a different logic here:
B4X:
For Each row As Map In DB.Results
    Dim tr1 As Tag = CreateProductsRow(row)
    tr1.up(tbody1)
Next

or maybe the root cause is in MiniHtml itself.

1763105572859.png


Logs:
B4X:
Emulated network latency: 100ms
EndsMeet server (version = 1.70) is running on port 8080
App version: 6.00
Open the following URL from your web browser
http://127.0.0.1:8080
Checking database...
SQLite database found!
GET: /
GET: /api/products/table
temp:  |  |
row: 1 | T001 | Teddy Bear
temp: 1 | T001 | Teddy Bear
row: 2 | H001 | Hammer
temp: 2 | H001 | Hammer
row: 3 | T002 | Optimus Prime
 

Attachments

  • Pakai_2025-11-14_1529.zip
    469.1 KB · Views: 23
Upvote 0

Magma

Expert
Licensed User
Longtime User
The problem is here:
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")
    Dim price As Double = data.Get("price")
    Dim category As String = data.Get("category")

    If App.ctx.ContainsKey("/table/row") = False Then
        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)
        tr1.add(Td.cls("align-middle"))'.text(category)
        tr1.add(Td.cls("align-middle").sty("text-align: right"))'.text(NumberFormat2(price, 1, 2, 2, True))
        Dim td6 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(td6)
        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(td6)
        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")
       
        ' Store for reuse
        App.ctx.Put("/table/row", tr1)
    End If
   
    Dim ctx As CopyOnWriteMap
    ctx.Initialize(App.ctx)
    Dim row1 As Tag = ctx.GetMap.Get("/table/row")
    Log($"temp: ${row1.Child(0).innerText} | ${row1.Child(1).innerText} | ${row1.Child(2).innerText}"$)
   
    ' Update tag with new data
    row1.Child(0).text2(id)
    row1.Child(1).text2(code)
    row1.Child(2).text2(name)
    row1.Child(3).text2(category)
    row1.Child(4).text2(NumberFormat2(price, 1, 2, 2, True))
    Dim td6 As Tag = row1.Child(5)
    Dim anchor1 As Tag = td6.Child(0)
    anchor1.hxGet($"/api/products/edit/${id}"$)
    Dim anchor2 As Tag = td6.Child(1)
    anchor2.hxGet($"/api/products/delete/${id}"$)
   
    Log($"row: ${row1.Child(0).innerText} | ${row1.Child(1).innerText} | ${row1.Child(2).innerText}"$)
    Return row1
End Sub

Maybe I need a different logic here:
B4X:
For Each row As Map In DB.Results
    Dim tr1 As Tag = CreateProductsRow(row)
    tr1.up(tbody1)
Next

or maybe the root cause is in MiniHtml itself.

View attachment 168357

Logs:
B4X:
Emulated network latency: 100ms
EndsMeet server (version = 1.70) is running on port 8080
App version: 6.00
Open the following URL from your web browser
http://127.0.0.1:8080
Checking database...
SQLite database found!
GET: /
GET: /api/products/table
temp:  |  |
row: 1 | T001 | Teddy Bear
temp: 1 | T001 | Teddy Bear
row: 2 | H001 | Hammer
temp: 2 | H001 | Hammer
row: 3 | T002 | Optimus Prime
i think you need a tr1.initialize (you must create one... at tag... that will reset to previous default map template - you ve mention)
 
Upvote 0

aeric

Expert
Licensed User
Longtime User
i think you need a tr1.initialize (you must create one... at tag... that will reset to previous default map template - you ve mention)
Tried with this code. Still the same.
B4X:
Dim ctx As CopyOnWriteMap
ctx.Initialize(App.ctx)
'Dim row1 As Tag = ctx.GetMap.Get("/table/row")
Dim row1 As Tag
row1.Initialize("tr")
row1 = ctx.GetMap.Get("/table/row")
Log($"temp: ${row1.Child(0).innerText} | ${row1.Child(1).innerText} | ${row1.Child(2).innerText}"$)
Maybe the problem is at somewhere else. probably during building the String.
 
Upvote 0

Magma

Expert
Licensed User
Longtime User
Tried with this code. Still the same.
B4X:
Dim ctx As CopyOnWriteMap
ctx.Initialize(App.ctx)
'Dim row1 As Tag = ctx.GetMap.Get("/table/row")
Dim row1 As Tag
row1.Initialize("tr")
row1 = ctx.GetMap.Get("/table/row")
Log($"temp: ${row1.Child(0).innerText} | ${row1.Child(1).innerText} | ${row1.Child(2).innerText}"$)
Maybe the problem is at somewhere else. probably during building the String.
but the Tag is your Sub... need to edit and in Initialize reset the template somehow...
 
Upvote 0

aeric

Expert
Licensed User
Longtime User
in Initialize reset the template somehow...
The (generated) "template" tag contains children/properties such as list and map for html styles and classes.
I don't want to clear the children.
 
Upvote 0

Magma

Expert
Licensed User
Longtime User
The (generated) "template" tag contains children/properties such as list and map for html styles and classes.
I don't want to clear the children.
may be you need to broke somehow or create initialize2("name",array(values))
 
Upvote 0

aeric

Expert
Licensed User
Longtime User
My intuition tells me there must be something simple that I overlook. 😆
 
Upvote 0

aeric

Expert
Licensed User
Longtime User
This sub is correct:
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")
    Dim price As Double = data.Get("price")
    Dim category As String = data.Get("category")

    If App.ctx.ContainsKey("/table/row") = False Then
        'Dim tr1 As Tag = Tr.init
        Dim tr1 As Tag
        tr1.Initialize("tr")
        tr1 = 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)
        tr1.add(Td.cls("align-middle"))'.text(category)
        tr1.add(Td.cls("align-middle").sty("text-align: right"))'.text(NumberFormat2(price, 1, 2, 2, True))
        Dim td6 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(td6)
        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(td6)
        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")
        
        ' Store for reuse
        App.ctx.Put("/table/row", tr1)
    End If
    
    Dim ctx As CopyOnWriteMap
    ctx.Initialize(App.ctx)
    'Dim row1 As Tag = ctx.GetMap.Get("/table/row")
    Dim row1 As Tag
    row1.Initialize("tr")
    row1 = ctx.GetMap.Get("/table/row")
    
    ' Update tag with new data
    row1.Child(0).text2(id)
    row1.Child(1).text2(code)
    row1.Child(2).text2(name)
    row1.Child(3).text2(category)
    row1.Child(4).text2(NumberFormat2(price, 1, 2, 2, True))
    Dim td6 As Tag = row1.Child(5)
    Dim anchor1 As Tag = td6.Child(0)
    anchor1.hxGet($"/api/products/edit/${id}"$)
    Dim anchor2 As Tag = td6.Child(1)
    anchor2.hxGet($"/api/products/delete/${id}"$)

    row1.PrintMe
    Return row1
End Sub
Logs:
B4X:
<tr>
  <td class="align-middle" style="text-align: right">1</td>
  <td class="align-middle">T001</td>
  <td class="align-middle">Teddy Bear</td>
  <td class="align-middle">Toys</td>
  <td class="align-middle" style="text-align: right">99.90</td>
  <td class="align-middle text-center px-1 py-1">
    <a class="edit text-primary mx-2" hx-get="/api/products/edit/1" hx-target="#modal-content" hx-trigger="click" data-bs-toggle="modal" data-bs-target="#modal-container" title="Edit">
      <i class="bi bi-pencil"></i></a>
    <a class="delete text-danger mx-2" hx-get="/api/products/delete/1" hx-target="#modal-content" hx-trigger="click" data-bs-toggle="modal" data-bs-target="#modal-container" title="Delete">
      <i class="bi bi-trash3"></i></a></td>
</tr>
<tr>
  <td class="align-middle" style="text-align: right">2</td>
  <td class="align-middle">H001</td>
  <td class="align-middle">Hammer</td>
  <td class="align-middle">Hardwares</td>
  <td class="align-middle" style="text-align: right">15.75</td>
  <td class="align-middle text-center px-1 py-1">
    <a class="edit text-primary mx-2" hx-get="/api/products/edit/2" hx-target="#modal-content" hx-trigger="click" data-bs-toggle="modal" data-bs-target="#modal-container" title="Edit">
      <i class="bi bi-pencil"></i></a>
    <a class="delete text-danger mx-2" hx-get="/api/products/delete/2" hx-target="#modal-content" hx-trigger="click" data-bs-toggle="modal" data-bs-target="#modal-container" title="Delete">
      <i class="bi bi-trash3"></i></a></td>
</tr>
<tr>
  <td class="align-middle" style="text-align: right">3</td>
  <td class="align-middle">T002</td>
  <td class="align-middle">Optimus Prime</td>
  <td class="align-middle">Toys</td>
  <td class="align-middle" style="text-align: right">1,000.00</td>
  <td class="align-middle text-center px-1 py-1">
    <a class="edit text-primary mx-2" hx-get="/api/products/edit/3" hx-target="#modal-content" hx-trigger="click" data-bs-toggle="modal" data-bs-target="#modal-container" title="Edit">
      <i class="bi bi-pencil"></i></a>
    <a class="delete text-danger mx-2" hx-get="/api/products/delete/3" hx-target="#modal-content" hx-trigger="click" data-bs-toggle="modal" data-bs-target="#modal-container" title="Delete">
      <i class="bi bi-trash3"></i></a></td>
</tr>

But when I log the generated table using this code:
B4X:
Private Sub CreateProductsTable As Tag
    If App.ctx.ContainsKey("/table") = False Then
        Dim table1 As Tag = HtmlTable.cls("table table-bordered table-hover rounded small")
        Dim thead1 As Tag = table1.add(Thead.cls("table-light"))
        thead1.add(Th.sty("text-align: right; width: 50px").text("#"))
        thead1.add(Th.text("Code"))
        thead1.add(Th.text("Name"))
        thead1.add(Th.text("Category"))
        thead1.add(Th.sty("text-align: right").text("Price"))
        thead1.add(Th.sty("text-align: center; width: 120px").text("Actions"))
        'Dim tbody1 As Tag = table1.add(Tbody.init)
        App.ctx.Put("/table", table1)
    End If
    Dim Template As CopyOnWriteMap
    Template.Initialize(App.ctx)
    Dim table1 As Tag = Template.GetMap.Get("/table")
    Dim tbody1 As Tag = table1.add(Tbody.init)

    DB.SQL = Main.DBOpen
    DB.Table = "tbl_products p"
    DB.Columns = Array("p.id id", "p.category_id catid", "c.category_name category", "p.product_code code", "p.product_name name", "p.product_price price")
    DB.Join = DB.CreateJoin("tbl_categories c", "p.category_id = c.id", "")
    DB.OrderBy = CreateMap("p.id": "")
    DB.Query
    For Each row As Map In DB.Results
        'Dim tr1 As Tag = CreateProductsRow(row)
        Dim tr1 As Tag
        tr1.Initialize("tr")
        tr1 = CreateProductsRow(row)
        tr1.up(tbody1)
    Next
    DB.Close
    table1.PrintMe
    Return table1
End Sub

I get the logs:
B4X:
GET: /
GET: /api/products/table
<table class="table table-bordered table-hover rounded small">
  <thead class="table-light">
    <th style="text-align: right; width: 50px">#</th>
    <th>Code</th>
    <th>Name</th>
    <th>Category</th>
    <th style="text-align: right">Price</th>
    <th style="text-align: center; width: 120px">Actions</th>
  </thead>
  <tbody>
    <tr>
      <td class="align-middle" style="text-align: right">3</td>
      <td class="align-middle">T002</td>
      <td class="align-middle">Optimus Prime</td>
      <td class="align-middle">Toys</td>
      <td class="align-middle" style="text-align: right">1,000.00</td>
      <td class="align-middle text-center px-1 py-1">
        <a class="edit text-primary mx-2" hx-get="/api/products/edit/3" hx-target="#modal-content" hx-trigger="click" data-bs-toggle="modal" data-bs-target="#modal-container" title="Edit">
          <i class="bi bi-pencil"></i></a>
        <a class="delete text-danger mx-2" hx-get="/api/products/delete/3" hx-target="#modal-content" hx-trigger="click" data-bs-toggle="modal" data-bs-target="#modal-container" title="Delete">
          <i class="bi bi-trash3"></i></a></td>
    </tr>
    <tr>
      <td class="align-middle" style="text-align: right">3</td>
      <td class="align-middle">T002</td>
      <td class="align-middle">Optimus Prime</td>
      <td class="align-middle">Toys</td>
      <td class="align-middle" style="text-align: right">1,000.00</td>
      <td class="align-middle text-center px-1 py-1">
        <a class="edit text-primary mx-2" hx-get="/api/products/edit/3" hx-target="#modal-content" hx-trigger="click" data-bs-toggle="modal" data-bs-target="#modal-container" title="Edit">
          <i class="bi bi-pencil"></i></a>
        <a class="delete text-danger mx-2" hx-get="/api/products/delete/3" hx-target="#modal-content" hx-trigger="click" data-bs-toggle="modal" data-bs-target="#modal-container" title="Delete">
          <i class="bi bi-trash3"></i></a></td>
    </tr>
    <tr>
      <td class="align-middle" style="text-align: right">3</td>
      <td class="align-middle">T002</td>
      <td class="align-middle">Optimus Prime</td>
      <td class="align-middle">Toys</td>
      <td class="align-middle" style="text-align: right">1,000.00</td>
      <td class="align-middle text-center px-1 py-1">
        <a class="edit text-primary mx-2" hx-get="/api/products/edit/3" hx-target="#modal-content" hx-trigger="click" data-bs-toggle="modal" data-bs-target="#modal-container" title="Edit">
          <i class="bi bi-pencil"></i></a>
        <a class="delete text-danger mx-2" hx-get="/api/products/delete/3" hx-target="#modal-content" hx-trigger="click" data-bs-toggle="modal" data-bs-target="#modal-container" title="Delete">
          <i class="bi bi-trash3"></i></a></td>
    </tr>
  </tbody>
</table>
 
Upvote 0
Top