B4J Tutorial [B4X, B4xPages]Using the Standard B4XPreferenceDialog Library with B4XPages to Create Effective Forms

If you are in a hurry, or if you don't want to design your own form, you can use the standard PreferencDialog
in combination with FormsBuilder to create a pretty nice form, one form per B4XPage.
https://www.b4x.com/android/forum/t...der-designer-for-b4xpreferencesdialog.104670/

This work was inspired by the recent wish expressed by @José J. Aguilar
https://www.b4x.com/android/forum/t...-into-a-b4xpage-instead-into-a-dialog.145685/

The principle behind this technique is to find ways of hiding the dialog foundation of the preferences CLV,
as well as to have more control over verification of answers.

This tutorial will show you how.

Here is my sample form. The B4XPage class is called ExampleForm, titled "Sample Form". I created example.json with FormsBuilder.

Screenshot.png


It doesn't look like a standard PreferenceDialog, but it is.
The title and buttons are empty strings. The colors are all set to the color of the form.

How can you Press OK when it isn't shown? How does the PreferenceDialog know it is finished?
When the B4XPage_CloseRequest is fired, I call prefdialog.Dialog.Close.
Before leaving the page, verification of answers takes place and if not valid the PreferenceDialog is shown again.

All of this is seemless. The user is informed of invalid answers, when OK to that message is pressed, a nifty wiggling
of the prompt draws attention to the item. I copied the wiggling sub from the PreferenceDialog source.

If all answers are present and valid, the form closes and the Example_Closed sub is called in the calling program.
This Sub can access the data and decide on further actions.

In the sample form I choose to leave the navigation to B4XPages, but you can remove the title panel and add your own buttons.
Search the Forum for information how to do that.

There are some bells and whistles that show how to make specific validity tests.
These tests are done inside the form module, keeping most of the logic in one module.
I have implemented a Maximum Tries parameter. Otherwise the user could not leave an invalid form (at least in B4J without using Task Manager).

You can unzip the B4XPages project and run it. It works in B4J and B4A (set for landscape or set for portrait).
Take a close look at the ExampleForm module. It has some features you may like to use in other situations.
 

Attachments

  • PrefFormsV3.zip
    29.3 KB · Views: 160
Last edited:

William Lancee

Well-Known Member
Licensed User
Longtime User
FormsBuilder saves its output as a .json file. B4X has a a JSON parser. JSON strings are self-structured.
Inter-element blank spaces, CRLF, and TABS are ignored. The output of FormBuilder is in the so-called 'pretty' form.
Which takes up more space than needed for this application. Take a look at the example.json file in File.DirAssets.
I included a Sub to read this file and put the following on the Log. Then I pasted the log in the top of the form module.
For minor changes, you can just make them in this string rather than going back to the FormsBuilder.

B4X:
    Dim spacer As String = $"{"title": "", "type": "Separator", "key": "", "required": false}"$
    Dim JString As String = $"
        {"Theme": "Light Theme",
            "Items": [
                {"title": "May I?", "type": "Boolean", "key": "proceed", "required": false},${spacer},
                {"title": "Your Name", "type": "Text", "key": "name", "required": true},${spacer},${spacer},${spacer},
                {"title": "When will you complete this?", "type": "Date", "key": "today", "required": false},${spacer},
                {"title": "Basic color?", "type": "Options", "key": "colors", "required": false,
                    "options": [White, Black, Red, Green, Blue]
                }
            ]
        }
    "$

'The conversion sub. It makes heavy use of the B4X smart string and JSONParser.

B4X:
Public Sub toWilFormat
    Dim JSON As JSONParser
    Dim Map1 As Map
    JSON.Initialize(File.ReadString(File.DirAssets, "example.json"))
    Map1 = JSON.NextObject
    Dim theme As String = Map1.Get("Theme")
    Dim items As List = Map1.Get("Items")
    Dim sb As StringBuilder
    sb.Initialize
    Dim spacerLiteral As String = "${spacer}"
    For Each item As Map In items
        Dim thistype As String = item.Get("type")
        If thistype = "Separator" Then
            If sb.Length > 0 Then sb.Remove(sb.Length - 1, sb.Length)
            sb.Append($"${spacerLiteral},"$).Append(CRLF)
        Else
            Dim thistitle As String = item.Get("title")
            Dim thiskey As String = item.Get("key")
            Dim thisrequired As String = item.Get("required")
            If item.containsKey("options") Then
                sb.Append("'                ").Append($"{"title": "${thistitle}", "type": "${thistype}", "key": "${thiskey}", "required": ${thisrequired},"$).Append(CRLF)
                sb.Append($"'                    "options": ["$)
                Dim optionList As List = item.Get("options")
                For Each s As String In optionList
                    sb.Append(s).Append(", ")
                Next
                If sb.Length > 0 Then sb.Remove(sb.Length - 2, sb.Length)
                sb.Append("]").Append(CRLF)
                sb.Append("'                }").Append(CRLF)
            Else
                sb.Append("'                ").Append($"{"title": "${thistitle}", "type": "${thistype}", "key": "${thiskey}", "required": ${thisrequired}},"$).Append(CRLF)
            End If
        End If
    Next
   
    Log("'    Dim spacer As String = $" & QUOTE & "{" & $""title": "", "type": "Separator", "key": "", "required": False}""$ & "$")
    Dim itemsString As String = sb.toString
    Dim lit1 As String = "'    JString = $" & QUOTE
    Dim lit2 As String = "'    " & QUOTE & "$"
   
    Dim WilJString As String = $"
${lit1}
'        {"Theme": "${theme}",
'            "Items": [
${itemsString}
'            ]
'        }
${lit2}
    "$
    Log(WilJString)
End Sub
 
Last edited:

William Lancee

Well-Known Member
Licensed User
Longtime User
The first version worked, but I have updated #1 & #2 to make the process clearer.

In B4XMainPage, this shows how to initialize the Example form, and how to use the results when it closes

B4X:
Private Sub B4XPage_Created (Root1 As B4XView)
    'In Class_Globals: Private Example As ExampleForm
    Root = Root1
    Example.Initialize(Me)
    B4XPages.AddPageAndCreate("example", Example)
    Sleep(0)
    B4XPages.ShowPage("example")
End Sub

#Region Form Results
Public Sub Example_Closed(NumRetries As Int, items As List)
    Log(NumRetries & TAB & items.Size & TAB & Example.MaxRetries)
    If NumRetries = Example.MaxRetries Then
        Log("Error: the user has exceeded the maximum tries and the form was closed")
        Return
    End If
    For i = 0 To items.Size - 1
        Dim pi As B4XPrefItem = items.Get(i)
        Dim itemName As String = pi.Key
        If pi.ItemType = Example.prefdialog.TYPE_SEPARATOR Then Continue
        Dim answer As Object = Example.data.Get(itemName)
        Select pi.ItemType
            Case Example.prefdialog.TYPE_DATE
                Log(itemName & TAB & DateTime.Date(answer))
            Case Else
                Log(itemName & TAB & answer)
        End Select
    Next
End Sub
#End Region
 
Last edited:

William Lancee

Well-Known Member
Licensed User
Longtime User
The B4XPage 'ExampleForm' module can be used as a template for your own forms. About 75% is common to all forms.
This sample form has only 200 lines of code. Nothing complicated to navigate. The structure is as follows:

B4X:
'#Regions 'Form Specification' and 'Form Validation' need to be modified for each form, the rest is common to all forms

#Region Class_Globals

Public Sub Initialize(CallBack_ As Object) As Object
    CallBack = CallBack_
   
#Region Form Specification  

    Return Me
End Sub

Private Sub B4XPage_Created (Root1 As B4XView)
    Root = Root1
   
#Region Form Vertical Layout and Colors (Common to all forms)

End Sub

Private Sub B4XPage_Appear
    If MaxRetries > -1 Then NumRetries = NumRetries + 1
    If NumRetries = MaxRetries Then
        CallSub3(CallBack, "Example_Closed", NumRetries, prefdialog.PrefItems)
        B4XPages.ClosePage(Me)
        Return
    End If
    pdia = prefdialog.ShowDialog(Data, "", "")                'Note that the normal Wait For is deferred to the B4XPage_CloseRequest sub
   
#Region CLV Colors (Common to all forms)

End Sub

Private Sub B4XPage_CloseRequest As ResumableSub
    prefdialog.Dialog.Close(xui.DialogResponse_Positive)    'The prefdialog won't finish by itself
    Wait For (pdia) Complete (Result As Int)                'Without this, the data won't be ready

#Region Form Validation

    CallSub3(CallBack, "Example_Closed", NumRetries, prefdialog.PrefItems)
    Return True
End Sub

#Region Optional JSON formater (Common to all forms)

#Region Utilities (Common to all forms)

Notes:

1. CallSub2(CallBack, "ExampleForm_Closed", NumTries, prefdialog.PrefItems) will be called on closing

2. If there are 'required' items, the Required note can be shown at the top of the form

3. If the user tries up to 'MaxRetries', the form will close
If you set it to -1 there will be no limits, but the user will not be able to proceed if there is an unfixed error

4. I would recommend not adding more fields than can fit on the form/screen. Scrolling would risk the user missing things.
It would be better to split a big form into smaller forms. That would be quick and easy to do with this approach.
It would keep the validation in smaller batches - making it more manageable for the user

That's it. I continue to be impressed by the ease of doing complicated things quickly with B4X.
 
Last edited:
Top