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 have a look around and found a nice one jsGrid which is feature rich, easy to use and fits rather well with the ABMaterial look and feel.
I already did the first part: modified the default CSS so it works with ABMaterial (checkboxes and combos did not work), created a basic class template and a sample on how to use it in a webapp.
Copy the CSS and JS files from the zip 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):
Creating even such a complex component is very easy in B4J with ABMaterial. Lets dive into some code:
The ABMTableGrid class
'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
Usage in a WebApp:
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() ... ' 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) ... 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) log(value.get("ID") End Sub Sub myTableGrid_Updated(value As Map) log(value.get("ID") End Sub Sub myTableGrid_Deleted(value As Map) log(value.get("ID") 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}"$) ... End Sub
As you can see, adding this rather complex component took me about 10 minutes to make, literally the time to put on a pot of Java. (pun intended!)
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 have 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.
Download the full source code and share your changes in this B4J Forum topic, so everyone can benefit from your creation.
Alwaysbusy