B4J Code Snippet [ABMaterial] Making an ABMCombo MultiSelect with ABMCheckboxes

Ola

So i am sitting and thinking... I need some multi-check box functionality in my combo box. I need to return the selected items and be able to set each item to be checked. Then it dawns on me. One of the nice things about the ABMCombo is the functionality to add ABMContainers to it. So it should be possible to add a container with a checkbox as an item. This basically is a nicer alternative for what I intend doing. I can trap the combo click event, get the selected items and process what I need to do.

ABMCombo.gif


So, I need a container that has a checkbox, I should be able to set the checkbox on and off on addition.

So I add this to my ABMShared..

B4X:
'add a checkbox item to combo
Sub ABMCheckBoxItem(page As ABMPage, id As String, text As String, checked As Boolean) As ABMContainer
    Dim ItemCont As ABMContainer
    ItemCont.Initialize(page, id, "")
    ItemCont.AddRowsM(1,False,0,20, "").AddCellsOSMP(1,0,0,0,12,12,12,0,0,10,10,"")
    ItemCont.BuildGrid 'IMPORTANT once you loaded the complete grid AND before you start adding components
 
    Dim chk As ABMCheckbox
    chk.Initialize(page, id,text,checked,"")
    ItemCont.Cell(1,1).AddComponent(chk)
    Return ItemCont
End Sub

Here I create a container with a checkbox, specifiying the id and text of the checkbox.

So in my connectpage, I create a combo and add some check boxes to it.

B4X:
Dim asel As ABMCombo
    asel.Initialize(page,"asel","Resources","650","")
    asel.AddItem("opt1","Option 1",ABMShared.ABMCheckBoxItem(page,"opt1","Option 1",True))
    asel.AddItem("opt2","Option 2",ABMShared.ABMCheckBoxItem(page,"opt2","Option 2",False))
    asel.AddItem("opt3","Option 3",ABMShared.ABMCheckBoxItem(page,"opt3","Option 3",True))
    asel.AddItem("opt4","Option 4",ABMShared.ABMCheckBoxItem(page,"opt4","Option 4",False))
    asel.AddItem("opt5","Option 5",ABMShared.ABMCheckBoxItem(page,"opt5","Option 5",False))
    page.Cell(2,1).AddComponent(asel)

Now, when the combo box is selected, I need to get the items that are checked in the combobox. So I add a click event. Off course, returning the selected items can be done anywhere in my code. The returned object is a list with the ids of each of the items selected.

B4X:
Sub asel_Clicked(itemId As String)
    Dim lst As List = ABMShared.ABMComboGetChecked(page,"asel")
    Dim out As String = ABMShared.List2JSON(lst)
    page.Msgbox("",out,"asel checked","ok",False,"","")
End Sub

As noted above, I have called ABMComboGetChecked passing the combo box name to it. To be able to do this I needed to write some JQuery.

B4X:
'get checked from combobox
Sub ABMComboGetChecked(page As ABMPage, id As String) As List
    Dim ulKey As String = $"dropdown${id}"$
    Dim script As String = $"var selected = [];
$('#${ulKey} input:checked').each(function() {
    selected.push($(this).attr('id'));
});
var myJSON = selected.toString();
return myJSON;"$
Dim f As Future = page.ws.EvalWithResult(script,Null)
Dim sout As String = f.value
Dim items() As String = StrParse(",",sout)
Dim lstItems As List
lstItems.Initialize
For Each strItem As String In items
    strItem = MvField(strItem,1,"-")
    lstItems.Add(strItem)
Next
Return lstItems
End Sub

When you right click on any HTML element on your page, you can see how its created. So the UL (unordered list) that stores the combo items is called dropdown + id of your component. The checkboxes are somewhere in that html element, so I call jquery to return all checked input ids inside the UL. Remember from this, the ABM dropdown is not a SELECT component but a UL.

Each element id is suffixed with -id+'input' being for example opt1-opt1input, so we need to return opt1, thus the mvfield function. This ends up being shown on the msgbox in the click event.

So, to be able to check anything in the combo, I also needed another method.

B4X:
Sub iif(Expression2Evaluate As String, ReturnIfTrue As Object, ReturnIfFalse As Object) As Object
    If Expression2Evaluate = True Then Return ReturnIfTrue Else Return ReturnIfFalse
End Sub

'set checked
Sub ABMComboSetChecked(page As ABMPage, compid As String, id As String, bChecked As Boolean)
    If id.length = 0 Then Return
    If id.StartsWith("[") And id.EndsWith("]") Then
        Dim lst As List = Json2List(id)
        id = MvFromList(lst,",")
    End If
    Dim parentID As String = $"dropdown${compid}"$
    'ability to set multiple items at once
    Dim spItems() As String = StrParse(",",id)
    Dim sbCode As StringBuilder
    sbCode.Initialize
    For Each strItem As String In spItems
        id = strItem
        Dim itmKey As String = $"${id}-${id}input"$
        Dim sChecked As String = iif(bChecked=True,"true","false")
        Dim script As String = $"$('#${parentID}').find('#${itmKey}').prop('checked', ${sChecked});"$
        sbCode.Append(script).Append(CRLF)
    Next
    page.ws.Eval(sbCode.tostring,Null)
    page.ws.flush
End Sub

Sub MvFromList(lst As List, Delim As String) As String
    Dim lTot As Int
    Dim lCnt As Int
    Dim lStr As StringBuilder
    lStr.Initialize
    lTot = lst.Size - 1
    For lCnt = 0 To lTot
        lStr.Append(lst.Get(lCnt))
        If lCnt <> lTot Then lStr.Append(Delim)
    Next
    Return lStr.tostring
End Sub

This I can call whenever I need to.

B4X:
ABMShared.ABMComboSetChecked(page, "cboItems"", "opt5",True)

As each element in a page needs a unique name, there can only be one opt5. This also creates the proper name for the element and then runs a JQuery script to check the element.

Should you wish to check elements in ConnectPage, ensure you do so after page.refresh.

Happy coding.

PS: In Summary, just to show how I have currently implemented this...

1. I build my normal modal sheet the same way it has been and add an ABMCombo in it.
2. On Add / Edit, I show the modal and clear the contents of the controls and refresh the ones I need where necessary. As an example, to refresh my combo /w checkboxes combo I call this method after I have called .ShowModalSheet. This selects records form a database and loads them to the combo, all the items are un-checked. This is both applicable for Add / Edit CRUD functions.

B4X:
'Refresh the contents of the ABMCombo at runtime.
Private Sub RefreshOnLoad_cboforums()
    'Get access to the component in the page.
    Dim msgenpeople As ABMModalSheet = page.ModalSheet("msgenpeople")
    Dim cboforums As ABMCombo = msgenpeople.Content.Component("cboforums")
    'Clear the ABMCombo items
    cboforums.Clear
    'Define list details to load to the combo
    Dim results As List
    Dim resCnt As Int
    Dim resTot As Int
    Dim resMap As Map
    'variable to hold the source field
    Dim id As String
    'variable to hold the description field
    Dim Name As String
    'Read arguments from LocalStorage (if any)
    'Add a spinner to the page
    'Get connection from current pool if MySQL/MSSQL
    Dim jSQL As SQL = ABMShared.SQLGet
    'Get the records as a list of maps from the db
    results = ABMShared.SQLExecuteMaps(jSQL,"select id,name from Forums  order by name", Null)
    'Close the connection to the database
    ABMShared.SQLClose(jSQL)
    'Loop throught each record read and process it
    resTot = results.size - 1
    For resCnt = 0 To resTot
        'Get the record map
        resMap = results.get(resCnt)
        'process the id field
        id = resMap.get("id")
        Name = resMap.get("name")
        cboforums.AddItem(id, Name, ABMShared.ABMCheckBoxItem(page, id, Name, False))
    Next
    'Refresh the ABMCombo contents
    cboforums.Refresh
End Sub

3. When a record is added / updated, I save the contents to a map which is used on my inserts database calls. To read the contents of the checked combo items, I call..

B4X:
Dim lstcboforums As List = ABMShared.ABMComboGetCheckedModal(page, "msgenpeople", "cboforums")
    Dim outcboforums As String = ABMShared.List2JSON(lstcboforums)
    pMap.put("forums",outcboforums)

The contents of outcboforums will be like ["1","2","4"] which will be ids of the checked items, this is put on the pMap map object and saved to the db.

4. When a record is selected for Edit i.e. ShowModal and view current record for editing, I read the db record to a map and then for the combo / w check, after it has been refreshed, just check the relevent items for this record.

B4X:
Dim cboforumsContents As String
    cboforumsContents = pMap.getdefault("forums","")
    ABMShared.ABMComboUnCheckAllModal(page, "msgenpeople","cboforums")
    ABMShared.ABMComboSetCheckedModal(page, "msgenpeople","cboforums", cboforumsContents,True)
 
Last edited:

MichalK73

Well-Known Member
Licensed User
Longtime User
There is a simpler method in my opinion.
In global class define 'Private listCombo As Map'.
B4X:
'Class module
Sub Class_Globals
    Private ws As WebSocket 'ignore
    ' will hold our page information
    Public page As ABMPage
    ' page theme
    Private theme As ABMTheme
    ' to access the constants
    Private ABM As ABMaterial 'ignore   
    ' name of the page, must be the same as the class name (case sensitive!)
    Public Name As String = "home"  '<-------------------------------------------------------- IMPORTANT
    ' will hold the unique browsers window id
    Private ABMPageId As String = ""
    ' your own variables   
    Private listCombo As Map  '<------ Variable added :)
End Sub

In WebSocket_Connect inicjalize map.
Inicjalize MAP:
#Region ABM
Private Sub WebSocket_Connected (WebSocket1 As WebSocket)   
    LogDebug("Connected")
    listCombo.Initialize   
    ws = WebSocket1       
    ....
    ...

Adding fields to a combo.
It is important that the ItemID of the combo box be the same as the ID of the checkbox.
B4X:
    Dim asel As ABMCombo
    asel.Initialize(page,"asel","Resources","650","")
    asel.AddItem("opt_"&1,"Option 1",ABMCheckBoxItem("opt_"&1,"Option 1",True))
    asel.AddItem("opt_"&2,"Option 2",ABMCheckBoxItem("opt_"&2,"Option 2",False))
    asel.AddItem("opt_"&3,"Option 3",ABMCheckBoxItem("opt_"&3,"Option 3",True))
    asel.AddItem("opt_"&4,"Option 4",ABMCheckBoxItem("opt_"&4,"Option 4",False))
    asel.AddItem("opt_"&5,"Option 5",ABMCheckBoxItem("opt_"&5,"Option 5",False))
    page.Cell(2,1).AddComponent(asel)

ABMCheckBoxItem:
Sub ABMCheckBoxItem( id As String, text As String, state As Boolean) As ABMContainer
    Dim ItemCont As ABMContainer
    ItemCont.Initialize(page, id, "")
    ItemCont.AddRowsM(1,False,0,20, "").AddCellsOSMP(1,0,0,0,12,12,12,0,0,10,10,"")
    ItemCont.BuildGrid 'IMPORTANT once you loaded the complete grid AND before you start adding components
     listCombo.Put(id,state)
    Dim chk As ABMCheckbox
    chk.Initialize(page, id,text, state,"")
    ItemCont.Cell(1,1).AddComponent(chk)
    Return ItemCont
End Sub

Now all you need is a read after selection from the combo.
B4X:
Sub asel_Clicked(itemId As String)
    Dim combo As ABMCombo = page.Component("asel")
    Dim container As ABMContainer = combo.GetComponent(itemId)
    Dim checkbox As ABMCheckbox = container.Component(itemId)
    listCombo.Put(itemId, checkbox.State)
    Dim j As JSONGenerator
    j.Initialize(listCombo)
    page.Msgbox("",j.ToPrettyString(2),"asel checked","ok",False,"","")
End Sub

No additional code is needed in JavaScript.
 

mauriegio

Member
Licensed User
Longtime User
There is a simpler method in my opinion.
In global class define 'Private listCombo As Map'.
B4X:
'Class module
Sub Class_Globals
    Private ws As WebSocket 'ignore
    ' will hold our page information
    Public page As ABMPage
    ' page theme
    Private theme As ABMTheme
    ' to access the constants
    Private ABM As ABMaterial 'ignore  
    ' name of the page, must be the same as the class name (case sensitive!)
    Public Name As String = "home"  '<-------------------------------------------------------- IMPORTANT
    ' will hold the unique browsers window id
    Private ABMPageId As String = ""
    ' your own variables  
    Private listCombo As Map  '<------ Variable added :)
End Sub

In WebSocket_Connect inicjalize map.
Inicjalize MAP:
#Region ABM
Private Sub WebSocket_Connected (WebSocket1 As WebSocket)  
    LogDebug("Connected")
    listCombo.Initialize  
    ws = WebSocket1      
    ....
    ...

Adding fields to a combo.
It is important that the ItemID of the combo box be the same as the ID of the checkbox.
B4X:
    Dim asel As ABMCombo
    asel.Initialize(page,"asel","Resources","650","")
    asel.AddItem("opt_"&1,"Option 1",ABMCheckBoxItem("opt_"&1,"Option 1",True))
    asel.AddItem("opt_"&2,"Option 2",ABMCheckBoxItem("opt_"&2,"Option 2",False))
    asel.AddItem("opt_"&3,"Option 3",ABMCheckBoxItem("opt_"&3,"Option 3",True))
    asel.AddItem("opt_"&4,"Option 4",ABMCheckBoxItem("opt_"&4,"Option 4",False))
    asel.AddItem("opt_"&5,"Option 5",ABMCheckBoxItem("opt_"&5,"Option 5",False))
    page.Cell(2,1).AddComponent(asel)

ABMCheckBoxItem:
Sub ABMCheckBoxItem( id As String, text As String, state As Boolean) As ABMContainer
    Dim ItemCont As ABMContainer
    ItemCont.Initialize(page, id, "")
    ItemCont.AddRowsM(1,False,0,20, "").AddCellsOSMP(1,0,0,0,12,12,12,0,0,10,10,"")
    ItemCont.BuildGrid 'IMPORTANT once you loaded the complete grid AND before you start adding components
     listCombo.Put(id,state)
    Dim chk As ABMCheckbox
    chk.Initialize(page, id,text, state,"")
    ItemCont.Cell(1,1).AddComponent(chk)
    Return ItemCont
End Sub

Now all you need is a read after selection from the combo.
B4X:
Sub asel_Clicked(itemId As String)
    Dim combo As ABMCombo = page.Component("asel")
    Dim container As ABMContainer = combo.GetComponent(itemId)
    Dim checkbox As ABMCheckbox = container.Component(itemId)
    listCombo.Put(itemId, checkbox.State)
    Dim j As JSONGenerator
    j.Initialize(listCombo)
    page.Msgbox("",j.ToPrettyString(2),"asel checked","ok",False,"","")
End Sub

No additional code is needed in JavaScript.
Thank so much

MichalK73

your code work very well .... my friend !!!
best regard

Maurizio​

 
Top