B4J Tutorial [ABMaterial] Custom component ABMTableGrid (team project)

alwaysbusy

Expert
Licensed User
One of the most asked for components in ABMaterial is an editable Grid. We already have ABMTable and ABMTableMutable, but they have their limitations when it comes to being editable. So, as this kind of object is huge, I suggest we make it a Team Effort to build one.

I did a look around and found a rather nice one jsGrid http://js-grid.com/ which is feature rich, easy to use and fits rather well with the ABMaterial look and feel.

I made the first part: modified the default CSS so it works with ABMaterial (checkboxes and combos did not work), created a basic start for the class and how to use it in a project.

Copy the CSS and JS files from the attachment into the css/custom/ and /js/custom/ folders of the ABMaterial www folder.

Start with localhost:51042/GridDemo


My current result looks like this (allows adding, deleting, changing, sorting and filtering):

upload_2017-2-6_12-34-40.png


The ABMTableGrid class (see also attachment):
B4X:
'Class module
Sub Class_Globals
   Public ABMComp As ABMCustomComponent
   Private cmbCountries As String
   Private DBdata As String
End Sub

'Initializes the object. Countries and data are Json Strings
Public Sub Initialize(InternalPage As ABMPage, ID As String, Countries As String, data As String)
   ABMComp.Initialize("ABMComp", Me, InternalPage, ID)
   cmbCountries = Countries
   DBdata = data
End Sub

Sub ABMComp_Build(internalID As String) As String
  Return $"<div id="${internalID}"></div><script>var _${internalID};</script>"$
End Sub

Sub ABMComp_FirstRun(InternalPage As ABMPage, internalID As String)
  Dim script As String = $"_${internalID} = $("#${internalID}").jsGrid({
  height: "100%",
  width: "100%",
  filtering: true,
     inserting: true,
  editing: true,
  sorting: true,
  paging: true,
  autoload: true,
  pageSize: 10,
  pageButtonCount: 5,
  deleteConfirm: "Do you really want to delete the client?",
  controller: {
       loadData: function(filter) {
         return $.grep(${DBdata}, function(client) {
          var fName = filter.Name.toUpperCase();
           var fAddress = filter.Address.toUpperCase();
           return (!filter.Name || client.Name.toUpperCase().indexOf(fName) > -1)
     && (!filter.Age || client.Age === filter.Age)
     && (!filter.Address || client.Address.toUpperCase().indexOf(fAddress) > -1)
     && (!filter.Country || client.Country === filter.Country)
     && (filter.Married === undefined || client.Married === filter.Married);
       });          
       },
       insertItem: function(insertingClient) {
        var json = JSON.stringify(insertingClient);
      b4j_raiseEvent('${ABMComp.ID}_inserted', {'value':json});
     },
     updateItem: function(updatingClient) {
         var json = JSON.stringify(updatingClient);
         b4j_raiseEvent('${ABMComp.ID}_updated', {'value':json});
       },
     deleteItem: function(deletingClient) {
         var json = JSON.stringify(deletingClient);
     b4j_raiseEvent('${ABMComp.ID}_deleted', {'value':json});
     }
     },
  fields: [
       { name: "Name", type: "text", width: 150 },
  { name: "Age", type: "number", width: 50 },
  { name: "Address", type: "text", width: 200 },
  { name: "Country", type: "select", items: ${cmbCountries}, valueField: "Id", textField: "Name" },
  { name: "Married", type: "checkbox", title: "Is Married", sorting: false},
  { type: "control" }
  ]
  });"$

  InternalPage.ws.Eval(script, Array As Object(ABMComp.ID))
  ' flush not needed, it's done in the refresh method in the lib
End Sub

public Sub SetDBData(data As String)
   DBdata = data
End Sub

Sub ABMComp_Refresh(InternalPage As ABMPage, internalID As String)
  Dim script As String = $"$("#${internalID}").jsGrid("refresh");"$
  InternalPage.ws.Eval(script, Null)
End Sub

' do the stuff needed when the object is removed
Sub ABMComp_CleanUp(InternalPage As ABMPage, internalID As String)

End Sub
Usage:
B4X:
Sub Class_Globals
   ...
   Dim myTableGrid As ABMTableGrid
   ' fake database for demo purposes
   Dim database As Map
End Sub

' make sure you copied the files from the .zip in the right folders
public Sub BuildPage()
   ...
   page.AddExtraCSSFile("custom/jsgrid.min.css")
   page.AddExtraCSSFile("custom/jsgrid-theme.min.css")
   page.AddExtraJavaScriptFile("custom/jsgrid.min.js")
   ...
End Sub

public Sub ConnectPage()
'   connecting the navigation bar


'   init all your own variables (like a List, Map) and add your components

   ' as for the demo, we fake a real database here
   LoadFakeData

   page.Cell(2,1).SetFixedHeight(500)

   ' for our combo, we add a Json string counting the countries
   myTableGrid.Initialize(page, "myTableGrid", $"[
  { Name: "", Id: 0 },
  { Name: "United States", Id: 1 },
  { Name: "Canada", Id: 2 },
  { Name: "United Kingdom", Id: 3 },
  { Name: "France", Id: 4 },
  { Name: "Brazil", Id: 5 },
  { Name: "China", Id: 6 },
  { Name: "Russia", Id: 7 }
  ]"$, LoadData)
     page.Cell(2,1).AddComponent(myTableGrid.ABMComp)

   ' refresh the page
   page.Refresh
   ' Tell the browser we finished loading
   page.FinishedLoading
   ' restoring the navigation bar position
   page.RestoreNavigationBarPosition
End Sub

Sub LoadData() As String
   ' we have to return a Json string
   Dim data As StringBuilder
   data.Initialize
   data.Append("[")
   For i = 0 To database.size - 1
     If i > 0 Then
       data.Append(",")
     End If
     data.Append(database.get(i))
   Next
   data.Append("]")
   Return data.ToString
End Sub

Sub myTableGrid_Inserted(value As Map)

End Sub

Sub myTableGrid_Updated(value As Map)

End Sub

Sub myTableGrid_Deleted(value As Map)

End Sub

Sub LoadFakeData()
   database.Initialize
   database.Put(0,$"{"ID": 0,"Name": "Otto Clay","Age": 61,"Country": 6,"Address": "Ap #897-1459 Quam Avenue","Married": false}"$)
   database.Put(1,$"{"ID": 1,"Name": "Connor Johnston","Age": 73,"Country": 7,"Address": "Ap #370-4647 Dis Av.","Married": false}"$)
   database.Put(2,$"{"ID": 2,"Name": "Lacey Hess","Age": 29,"Country": 7,"Address": "Ap #365-8835 Integer St.","Married": false}"$)
   database.Put(3,$"{"ID": 3,"Name": "Timothy Henson","Age": 78,"Country": 1,"Address": "911-5143 Luctus Ave","Married": false}"$)
   database.Put(4,$"{"ID": 4,"Name": "Ramona Benton","Age": 43,"Country": 5,"Address": "Ap #614-689 Vehicula Street","Married": true}"$)
   database.Put(5,$"{"ID": 5,"Name": "Ezra Tillman","Age": 51,"Country": 1,"Address": "P.O. Box 738, 7583 Quisque St.","Married": true}"$)
   database.Put(6,$"{"ID": 6,"Name": "Dante Carter","Age": 59,"Country": 1,"Address": "P.O. Box 976, 6316 Lorem, St.","Married": false}"$)
   database.Put(7,$"{"ID": 7,"Name": "Christopher Mcclure","Age": 58,"Country": 1,"Address": "847-4303 Dictum Av.","Married": true}"$)
   database.Put(8,$"{"ID": 8,"Name": "Ruby Rocha","Age": 62,"Country": 2,"Address": "5212 Sagittis Ave","Married": false}"$)
   database.Put(9,$"{"ID": 9,"Name": "Imelda Hardin","Age": 39,"Country": 5,"Address": "719-7009 Auctor Av.","Married": false}"$)
   database.Put(10,$"{"ID": 10,"Name": "Jonah Johns","Age": 28,"Country": 5,"Address": "P.O. Box 939, 9310 A Ave","Married": false}"$)
   database.Put(11,$"{"ID": 11,"Name": "Herman Rosa","Age": 49,"Country": 7,"Address": "718-7162 Molestie Av.","Married": true}"$)
   database.Put(12,$"{"ID": 12,"Name": "Arthur Gay","Age": 20,"Country": 7,"Address": "5497 Neque Street","Married": false}"$)
   database.Put(13,$"{"ID": 13,"Name": "Xena Wilkerson","Age": 63,"Country": 1,"Address": "Ap #303-6974 Proin Street","Married": true}"$)
   database.Put(14,$"{"ID": 14,"Name": "Lilah Atkins","Age": 33,"Country": 5,"Address": "622-8602 Gravida Ave","Married": true}"$)
   database.Put(15,$"{"ID": 15,"Name": "Malik Shepard","Age": 59,"Country": 1,"Address": "967-5176 Tincidunt Av.","Married": false}"$)
   database.Put(16,$"{"ID": 16,"Name": "Keely Silva","Age": 24,"Country": 1,"Address": "P.O. Box 153, 8995 Praesent Ave","Married": false}"$)
   database.Put(17,$"{"ID": 17,"Name": "Hunter Pate","Age": 73,"Country": 7,"Address": "P.O. Box 771, 7599 Ante, Road","Married": false}"$)
   database.Put(18,$"{"ID": 18,"Name": "Mikayla Roach","Age": 55,"Country": 5,"Address": "Ap #438-9886 Donec Rd.","Married": true}"$)
   database.Put(19,$"{"ID": 19,"Name": "Upton Joseph","Age": 48,"Country": 4,"Address": "Ap #896-7592 Habitant St.","Married": true}"$)
   database.Put(20,$"{"ID": 20,"Name": "Jeanette Pate","Age": 59,"Country": 2,"Address": "P.O. Box 177, 7584 Amet, St.","Married": false}"$)
   database.Put(21,$"{"ID": 21,"Name": "Kaden Hernandez","Age": 79,"Country": 3,"Address": "366 Ut St.","Married": true}"$)
   database.Put(22,$"{"ID": 22,"Name": "Kenyon Stevens","Age": 20,"Country": 3,"Address": "P.O. Box 704, 4580 Gravida Rd.","Married": false}"$)
   database.Put(23,$"{"ID": 23,"Name": "Jerome Harper","Age": 31,"Country": 5,"Address": "2464 Porttitor Road","Married": false}"$)
   database.Put(24,$"{"ID": 24,"Name": "Jelani Patel","Age": 36,"Country": 2,"Address": "P.O. Box 541, 5805 Nec Av.","Married": true}"$)
   database.Put(25,$"{"ID": 25,"Name": "Keaton Oconnor","Age": 21,"Country": 1,"Address": "Ap #657-1093 Nec, Street","Married": false}"$)
   database.Put(26,$"{"ID": 26,"Name": "Bree Johnston","Age": 31,"Country": 2,"Address": "372-5942 Vulputate Avenue","Married": false}"$)
   database.Put(27,$"{"ID": 27,"Name": "Maisie Hodges","Age": 70,"Country": 7,"Address": "P.O. Box 445, 3880 Odio, Rd.","Married": false}"$)
   database.Put(28,$"{"ID": 28,"Name": "Kuame Calhoun","Age": 39,"Country": 2,"Address": "P.O. Box 609, 4105 Rutrum St.","Married": true}"$)
   database.Put(29,$"{"ID": 29,"Name": "Carlos Cameron","Age": 38,"Country": 5,"Address": "Ap #215-5386 A, Avenue","Married": false}"$)
   database.Put(30,$"{"ID": 30,"Name": "Fulton Parsons","Age": 25,"Country": 7,"Address": "P.O. Box 523, 3705 Sed Rd.","Married": false}"$)
   database.Put(31,$"{"ID": 31,"Name": "Wallace Christian","Age": 43,"Country": 3,"Address": "416-8816 Mauris Avenue","Married": true}"$)
   database.Put(32,$"{"ID": 32,"Name": "Caryn Maldonado","Age": 40,"Country": 1,"Address": "108-282 Nonummy Ave","Married": false}"$)
   database.Put(33,$"{"ID": 33,"Name": "Whilemina Frank","Age": 20,"Country": 7,"Address": "P.O. Box 681, 3938 Egestas. Av.","Married": true}"$)
   database.Put(34,$"{"ID": 34,"Name": "Emery Moon","Age": 41,"Country": 4,"Address": "Ap #717-8556 Non Road","Married": true}"$)
   database.Put(35,$"{"ID": 35,"Name": "Price Watkins","Age": 35,"Country": 4,"Address": "832-7810 Nunc Rd.","Married": false}"$)
End Sub
So this is were you guys can come in!

Some suggestions:

1. Adapt my class so the fields are generic (maybe by passing a Map with field definitions in initialize and then 'build' the correct JavaScript string in FirstRun.
2. I only set some basic parameters (like sorting, filtering etc), but this component has a lot more to offer. They can become properties of the class. See the jsGrid link at the top of this article.

And please share your changes in this topic, so everyone can profit from it!

Lets do this :)
 

Attachments

Last edited:

Mashiane

Expert
Licensed User
Hi there...

I have just taken a look at this and tried your suggestions. I'm however stuck with the 'select' type, for some reason (bang head), it's showing [object object]

TableGrid.png


This is what has been achieved so far...

1. Dynamic Column addition including name and title and width and alignment. MashBuildColumnsMethod
2. Added some properties for the grid for its options e.g. filtering, hide headings etc.
3. Added a 'gender' selection column to the demo..

Known Issues

[Object object] showing on select fields: When i do a msgbox, the right content appears though.

I'd like to use this and would use some advise, thanks for this component.

Kind Regards

Mashy

The code..

1. Text columns are left aligned by default
2. Number columns are right aligned
3. Checkboxes are centered
4.1 Initialize the abmtablegrid... InitializeMash
4.2 Add the columns with their respective types
4.3 If you have a select, initialize it with the items (after adding the its column)
4.4. Set the data

B4X:
myTableGrid.InitializeMash(page,"mashgrid")
    'lets add the columns
    myTableGrid.MashAddText("Name","Full Name",150)
    myTableGrid.MashAddNumber("Age","Age",50)
    myTableGrid.MashAddText("Address","Residential Address",200)
    myTableGrid.MashAddSelect("Country","Country of Birth",100)
    myTableGrid.MashAddCheckBox("Married","Married?",60)
    'myTableGrid.MashAddSelect("Gender","Gender","")
    myTableGrid.MashAddControl
 
     Dim countries As Map
    countries.Initialize
    countries.put("0","")
    countries.put("1","United States")
    countries.put("2","Canada")
    countries.put("3","United Kingdom")
    countries.put("4","France")
    countries.put("5","Brazil")
    countries.put("6","China")
    countries.put("7","Russia") 
    myTableGrid.MashSetSelect("Country","Id","Name", countries)
 
    Dim gender As Map
    gender.Initialize
    gender.Put("0","")
    gender.Put("1","Male")
    gender.Put("2","Female")
    myTableGrid.MashSetSelect("Gender","Id","Name", gender)
    'myTableGrid.Heading = False
    'myTableGrid.Filtering = True
    myTableGrid.SetDBData(LoadData)
 
      page.Cell(2,1).AddComponent(myTableGrid.ABMComp)
To run, copy the www folder (ABMaterial 3.75)
 

Attachments

PCastagnetti

Member
Licensed User
I'm however stuck with the 'select' type, for some reason (bang head), it's showing [object object]

I attach the code that fixes the problem (only for sample)

B4X:
private Sub MashJoin(lst As List, Delim As String) As String
    Dim lStr As StringBuilder
    lStr.Initialize
    For Each strValue As String In lst
        strValue= strValue.Replace("{Name: ","")
        strValue=strValue.SubString2(0,strValue.IndexOf(","))
        lStr.Append(strValue).Append(Delim)
    Next
    If lStr.ToString.EndsWith(Delim) Then
        lStr.Remove(lStr.Length-Delim.length,lStr.Length)
    End If
    Return lStr.tostring
End Sub
I know it would correct the incoming list, but it was just to show how the string should be created

items: ["", "United States", "Canada", "United Kingdom"]

instead of

items: [{Name: "", Id: 0},{Name: "United States", Id: 1},{Name: "Canada", Id: 2},{Name: "United Kingdom", Id: 3}]



I hope it can be useful
 

Mashiane

Expert
Licensed User
I attach the code that fixes the problem (only for sample)

B4X:
private Sub MashJoin(lst As List, Delim As String) As String
    Dim lStr As StringBuilder
    lStr.Initialize
    For Each strValue As String In lst
        strValue= strValue.Replace("{Name: ","")
        strValue=strValue.SubString2(0,strValue.IndexOf(","))
        lStr.Append(strValue).Append(Delim)
    Next
    If lStr.ToString.EndsWith(Delim) Then
        lStr.Remove(lStr.Length-Delim.length,lStr.Length)
    End If
    Return lStr.tostring
End Sub
I know it would correct the incoming list, but it was just to show how the string should be created

items: ["", "United States", "Canada", "United Kingdom"]

instead of

items: [{Name: "", Id: 0},{Name: "United States", Id: 1},{Name: "Canada", Id: 2},{Name: "United Kingdom", Id: 3}]



I hope it can be useful
Thanks, but from the look of things its expecting the correct select definition. If you look at the data, the country is numeric and thus links back to the defined names. After taking another look, I realized that everything is in fact 'case SenSitive', after fixing that it worked. The issue however is that whilst in edit mode you are able to add a record and select an item from the list, it does not show on the saved record select control but as you click 'edit' it shows. Still investigating. Thanks for the heads up.

PS: Sadly this component is not responsive so one can only use it with PCs it seems.
 

Mashiane

Expert
Licensed User
Latest, we are able to add column dynamically and the filter part of the component builds the filters also based on the columns added.

In this example I tried to add a date component, dololo. The gender seems to work ok but it does not display saved data from the table, I think its because of the structure of the data. I will try and match that and see what happens.

B4X:
myTableGrid.InitializeMash(page,"mashgrid")
    'lets add the columns
    myTableGrid.MashAddText("Name","Full Name",150)
    myTableGrid.MashAddNumber("Age","Age",50)
    myTableGrid.MashAddDate("HireDate","Hire Date",60)
    myTableGrid.MashAddText("Address","Residential Address",200)
    myTableGrid.MashAddSelect("Country","Country of Birth",100)
    myTableGrid.MashAddCheckBox("Married","Married?",60)
    myTableGrid.MashAddSelect("Gender","Gender",60)
    myTableGrid.MashAddRating("Rating","Rating",50)
    myTableGrid.MashAddControl
 
    'the ids should be added as string
    Dim countries As Map
    countries.Initialize
    countries.put("0","")
    countries.put("1","United States")
    countries.put("2","Canada")
    countries.put("3","United Kingdom")
    countries.put("4","France")
    countries.put("5","Brazil")
    countries.put("6","China")
    countries.put("7","Russia") 
    countries.Put("8", "South Africa")
    myTableGrid.MashSetSelect("Country","Id","Name", countries)
 
    'ids should be added as string
    Dim gender As Map
    gender.Initialize
    gender.Put("0","")
    gender.Put("1","Male")
    gender.Put("2","Female")
    myTableGrid.MashSetSelect("Gender","Id","Name", gender)
    'myTableGrid.Heading = False
    'myTableGrid.Filtering = True
    myTableGrid.SetDBData(LoadData)
 
    myTableGrid.MashSetRequired("name",True)
    myTableGrid.MashSetRequired("age", True)
    myTableGrid.MashSetRequired("country", True)
    myTableGrid.MashSetNumericValueType("country")
    myTableGrid.MashSetSelectedIndex("Country","8")
 
    page.Cell(2,1).AddComponent(myTableGrid.ABMComp)
TableGrid.png
 

Attachments

Mashiane

Expert
Licensed User
Hi Mashiane: very good In this example but Sub myTableGrid_Updated(value As Map) ,can't use
What is the name that you have used in your .Initialize method, in my example its mashgrid so my event will be

B4X:
mashgrid_updated(value as Map) etc.
Honestly I did'nt test the events for this, I'm sure I can cook up a CRUD example soon.
 

Mashiane

Expert
Licensed User
@micro . Looking into the last zip file, a couple of things I'm picking up. That example was done with version 3.75 of the ABM library. After that there was 4.03 and 4.25 and this was not taken forward. As you might have noted from my comments on that post, there was a lot of other things that were not working with the version that I tried to finalize for the 'community' and thus never looked into this much further, the cache system it uses is 2 whilst we currently have 3 for example.

Which version of ABM are you using?
 

Mashiane

Expert
Licensed User
Cool, just ensure that the columns being added to the grid are the same as the ones in the data and follows the sequencing there. It should look like this on connect page. I think I was testing other field definitions and then added more columns.

B4X:
myTableGrid.MashAddText("Name","Full Name",150)
    myTableGrid.MashAddNumber("Age","Age",50)
    myTableGrid.MashAddSelect("Country","Country of Birth",100)
    myTableGrid.MashAddText("Address","Residential Address",200)
    myTableGrid.MashAddCheckBox("Married","Married?",60)
    myTableGrid.MashAddControl
 
Top