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
Code:
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
...
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
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
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
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
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
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.
[CODE lang="b4x" highlight="3"]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[/CODE]
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.
[CODE lang="b4x" highlight="2,7"]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[/CODE]
 
Upvote 0
 
Upvote 0
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

Upvote 0
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
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.
[CODE lang="b4x" highlight="5"]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}"$)[/CODE]
Maybe the problem is at somewhere else. probably during building the String.
 
Upvote 0
Tried with this code. Still the same.
[CODE lang="b4x" highlight="5"]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}"$)[/CODE]
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
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
This sub is correct:
[CODE lang="b4x" highlight="61"]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[/CODE]
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:
[CODE lang="b4x" highlight="33"]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[/CODE]

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
Back
Top