B4J Tutorial [BANanoVuetifyAD3] : VFlexDialog inspired by the PreferenceDialog

Hi there

The VFlexDialog is inspired by the ease of use in the PreferenceDialog. It exists to enable the ease of creating forms by developers. Its baseline are the VDialog, VForms and VFields. If you have used any of these including the VMsgBox, then you are right at home.

The screen shots below have all been created by just calling utility functions to build the Dialog. Each dialog has a title, sub-title, yes, no, cancel buttons and these are dynamic and changeable via run time.

The only ceveat about the VFlexDialog is that you have to create your controls on Initialize of your page because its based on the functionality of the VForm + VField. Unlike the Dynamic Form which can be created at runtime, this is specific to Design Time creation.

To use it, drop a VFlexDialog inside a VContainer, specify the Title, Sub-Title, Yes/No/Cancel captions, colours, visibility etc and then create your Dialog Structure. You can also place controls side by side as demonstrated in these examples.

NB: The elements on the VFlexDialog as VForm + VFields, so their respective events will apply.

Example 1



The source code that produces this UI is...

In Class_Globals

B4X:
    Private txtfirstname As VField     'ignore
    Private txtlastname As VField     'ignore
    Private txtbio As VField     'ignore
    Private cbolang As VField     'ignore
    Private colhair As VField     'ignore
    Private dpdob As VField     'ignore
    Private txtheight As VField     'ignore
    Private filimage As VField     'ignore
    Private imgprofile As VField     'ignore
    Private radgender As VField     'ignore
    Private rngsalary As VField     'ignore
    Private ratservice As VField     'ignore
    Private sldloan As VField     'ignore
    Private teltel As VField     'ignore
    Private timmeet As VField     'ignore
    Private pwdpass As VField     'ignore
    Private swtactive As VField     'ignore
    Private chkagree As VField     'ignore
    Private VFlexDialog1 As VFlexDialog

In Initialize

B4X:
Sub BuildPreferences
    VFlexDialog1.Clear
    'create options
    Dim options As Map = CreateMap()
    options.Put("eng", "English")
    options.Put("afr", "Afrikaans")
    options.Put("xho", "Xhosa")
    options.Put("nde", "isiNdebele")
    options.Put("zul", "isiZulu")
    options.Put("sep", "Sepedi")
    options.Put("ses", "Sesotho")
    options.Put("tsw", "Setswana")
    options.Put("swa", "seSwati")
    options.Put("tsh", "Tshivenda")
    options.Put("xit", "Xitsonga")
   
    'ROW 1
    VFlexDialog1.AddTextField(1, 1, "firstname", "First Name","Anele")
    VFlexDialog1.AddTextField(1, 2, "lastname", "First Name","Mbanga")
    'ROW 2
    VFlexDialog1.AddTextArea(2, 1, "bio", "Biography","")
    'ROW 3
    VFlexDialog1.AddAutoComplete(3, 1, "lang", "Language", options, "eng", True)
    VFlexDialog1.AddColor(3, 2, "hair", "Enter hair color","black")
    'ROW 4
    VFlexDialog1.AddDate(4, 1, "dob", "Enter Date of Birth","1973-04-15")
    VFlexDialog1.AddDouble(4, 2, "height", "Enter your height","1.85")
    'ROW 5
    VFlexDialog1.AddFileInput(5, 1,"image", "Please upload your image")
    VFlexDialog1.AddImage(5, 2, "profile", "./assets/myself.jpg", "100", "100")
    '
    Dim gender As Map = CreateMap()
    gender.Put("male", "Male")
    gender.Put("female", "Female")
    gender.Put("other", "Other")
    'ROW 6
    VFlexDialog1.AddRadioGroup(6, 1, "gender", "Gender", gender,"male", True)
    VFlexDialog1.AddRange(6, 2, "salary", "Salary Range", 5000, 10000, 100, 6000)
   
    'ROW 7
    VFlexDialog1.AddRating(7, 1, "service", 5, "How can you rate our service?", 2)
    VFlexDialog1.AddSlider(7, 2, "loan", "Loan amount", 1000, 5000, 50, 1500)
   
    'ROW 8
    VFlexDialog1.AddTelephone(8, 1, "tel", "Telephone", "")
    VFlexDialog1.AddTime(8, 2, "meet", "Time of Meeting","09:00")
   
    'ROW 9
    VFlexDialog1.AddPassword(9, 1, "pass", "Enter your password","", True)
   
    'ROW 10
    VFlexDialog1.AddSwitch(10, 1, "active","Active", True, True)
    VFlexDialog1.AddCheckBox(10, 2, "agree", "I agree with Terms & Conditions",True)
    '
    VFlexDialog1.Refresh
    'get the vFields for bindings
    'COMPULSORY - get the created views to link events to them and also execute automatic bindstate.
    txtfirstname = VFlexDialog1.Get("firstname")
    txtlastname = VFlexDialog1.Get("lastname")
    txtbio = VFlexDialog1.Get("bio")
    cbolang = VFlexDialog1.Get("lang")
    colhair = VFlexDialog1.Get("hair")
    dpdob = VFlexDialog1.Get("dob")
    txtheight = VFlexDialog1.Get("height")
    filimage = VFlexDialog1.Get("image")
    imgprofile = VFlexDialog1.Get("profile")
    radgender = VFlexDialog1.Get("gender")
    rngsalary = VFlexDialog1.Get("salary")
    ratservice = VFlexDialog1.Get("service")
    sldloan = VFlexDialog1.Get("loan")
    teltel = VFlexDialog1.Get("tel")
    timmeet = VFlexDialog1.Get("meet")
    pwdpass = VFlexDialog1.Get("pass")
    swtactive = VFlexDialog1.Get("active")
    chkagree = VFlexDialog1.Get("agree")
    'Log(VFlexDialog1.Declarations)
    'Log(VFlexDialog1.Gets)
End Sub

Things to Note

Each element is defined to sit inside a grid. For example,

B4X:
VFlexDialog1.AddTextField(1, 1, "firstname", "First Name","Anele")

This means the element will sit on R1C1.

Automatic BindState

As the elements created are VFields, they are bound by those terms and conditions. To be able to effect automatic bindstate in this case, after the building of the VFlexDialog by the .Refresh call

B4X:
 VFlexDialog1.Refresh

We need to get each view created by the VFlexDialog, with calls like...

B4X:
    pwdpass = VFlexDialog1.Get("pass")

This is mandatory for this to work.

Ceveat: The code to create the elements is not direct use of the Abstract Designer, so the variable declarations in Class_Globals are manual, thus we cannot generate members.
However this does not stop one from dropping VFields with similar names on the abstract designer & then generating members & then remove them to avoid a conflict.

Events

As an example, when one clicks this element..



The File Selector is activated and once someone selects a file, it is uploaded to the assets folder. The name of the element that is a FileInput is named "image", so a image_change event will be trapped. Uploading the file to the server. This goes exactly the same with buttons and other events.

B4X:
Private Sub image_Change(fileObj As Map)
    page.refs = vuetify.GetRefs
    'has the file been specified
    If banano.IsNull(fileObj) Or banano.IsUndefined(fileObj) Then Return
    'show the loading indicator
    filimage.UpdateLoading(page, True)
    'get file details
    Dim fileDet As FileObject
    fileDet = BANanoShared.GetFileDetails(fileObj)
    'get the file name
    Dim fn As String = fileDet.FileName
    'you can check the size here
    Dim fs As Long = fileDet.FileSize
    If fs >= 5000 Then
    End If
    'start uploading the file to assets folder
    fileDet = BANanoShared.UploadFileWait(fileObj)
    'get the status of the upload
    Dim sstatus As String = fileDet.Status
    Select Case sstatus
        Case "error"
            'hide the uploader
            filimage.UpdateLoading(page, False)
            vuetify.ShowSnackBarError($"The file '${fn}' was not uploaded successfully!"$)
        Case "success"
            vuetify.ShowSnackBarSuccess($"The file '${fn}' was uploaded successfully!"$)
    End Select
    'get the upload full path
    Dim fp As String = fileDet.FullPath
    'update state of some element like an image
    imgprofile.SetValue(page, fp)
    'hide the uploading status
    filimage.UpdateLoading(page, False)
End Sub

PLEASE SEE POST #7 FOR IMPORTANT UPDATES
 
Last edited:

Mashiane

Expert
Licensed User
Longtime User
Example 2



In this example, we have added an avatar and centered it inside its parent RC. This is done with..

B4X:
VFlexDialog1.AddAvatar(1, 1, "myself", "./assets/myself.jpg", "100")
    'center the avatar on the RC
    VFlexDialog1.SetItemCenterOnParent("myself", True)

We also have a button that fills the block and its blue and white. We have created this button like this

B4X:
'add a button
    VFlexDialog1.AddButton(1, 2, "btnSend", "Send Email", True, False, True, True)
    'set the colors
    VFlexDialog1.SetItemColors("btnsend", vuetify.COLOR_BLUE, vuetify.INTENSITY_NORMAL, vuetify.COLOR_WHITE, vuetify.INTENSITY_NORMAL)

When this button is clicked, it fires an event. Remember, we had defined the button in class globals, it was then Added to the VFlexDialog and the VFlexDialog.Refresh created the actual button. We then linked the button definition in Class_Globals with the created button when we got it with

B4X:
btnSend = VFlexDialog1.Get("btnsend")

We have to do this ourselves this time around. When you use the Abstract Designer and call LoadLayout, this is all done automatically for you.

When you double click the btnSend ... VField, all the applicable events are listed by intellisence.



We update our btnSend event..

B4X:
Sub btnSend_click(e As BANanoEvent)
    vuetify.ShowSwalInfo("Send an email...")
End Sub

So that it executes whatever code we want...

To be able to show the dialog, we call its show method..

B4X:
Private Sub btnShowDialog_Click (e As BANanoEvent)
    VFlexDialog1.Show
End Sub

To trap the Yes/ No / Cancel click events, we define the events from VFlexDialog..

B4X:
Private Sub VFlexDialog1_Yes_Click (e As BANanoEvent)
    VFlexDialog1.Hide
End Sub

Private Sub VFlexDialog1_No_Click (e As BANanoEvent)
    VFlexDialog1.Hide
End Sub

Private Sub VFlexDialog1_Cancel_Click (e As BANanoEvent)
    VFlexDialog1.Hide
End Sub
 

Mashiane

Expert
Licensed User
Longtime User
Example 3



This example mainly deals with display elements, icons, avatar texts, chips and progress bars.

The code is also easy to follow...

B4X:
Sub BuildForm
    VFlexDialog1.Clear
    '
    VFlexDialog1.AddProgressCircular(1, 1, "prjcomplete", "100", "5", 45)
    VFlexDialog1.AddProgressLinear(1, 2, "prjcomplete1", "15px", 45)
    VFlexDialog1.SetItemColors("prjcomplete1", "black", "normal", "white", "normal")
    VFlexDialog1.SetItemColors("prjcomplete", "blue", "normal", "blue", "normal")
    '
    VFlexDialog1.AddMinusPlus(2, 1, "quantity", "10", "", False)
    VFlexDialog1.AddChip(2, 2, "quantityx", "Chip X", "", True)
    VFlexDialog1.SetItemColors("quantityx", "blue", "normal", "white", "normal")
    'VFlexDialog1.AddChipAvatar(2, 3, "chpavatar", "./assets/myself.jpg", "Mashy", "", False)
    VFlexDialog1.SetItemColors("quantity", "green", "normal", "white", "normal")
    '
    VFlexDialog1.AddButtonIcon(3, 1, "btnIcon1", "mdi-vuetify", True, False).SetItemSize("btnIcon1", vuetify.SIZE_XLARGE)
    VFlexDialog1.AddFileInputButton(3, 2, "upload", "Upload")
    '
    VFlexDialog1.AddButtonTextIcon(4, 1, "btnIcon2", "Left Icon", "mdi-stadium-outline", "left", True, False, False, False)
    VFlexDialog1.AddButtonTextIcon(4, 2, "btnIcon3", "Right Icon", "mdi-file-tree", "right", True, False, False, False)
    '
    VFlexDialog1.AddButtonTextImage(5, 1, "btnImage1", "Left Image", "./assets/img5.png", "left", "circle",  True, False, True, False)
    VFlexDialog1.AddButtonTextImage(5, 2, "btnImage2", "Right Image", "./assets/img8.png", "right", "rounded", True, False, False, False)
    '
    VFlexDialog1.AddIcon(6, 1, "wifi", "mdi-plane-car", vuetify.SIZE_LARGE, "", True).SetItemColors("wifi", "orange", vuetify.INTENSITY_DARKEN1, "", "")
    VFlexDialog1.AddIcon(6, 2, "wifi1", "mdi-plane-car", "", "200", True).SetItemColors("wifi1", "purple", vuetify.INTENSITY_DARKEN1, "", "")
    '
    VFlexDialog1.AddAvatarIcon(7, 1, "ano", "mdi-cards-heart", "48").SetItemColors("ano", "pink", vuetify.INTENSITY_DARKEN1, "", "")
    VFlexDialog1.AddAvatarText(7, 2, "ano1", "AM", "60").SetItemColors("ano1", "brown", vuetify.INTENSITY_DARKEN1, "white", "normal")
    
    '
    VFlexDialog1.Refresh
    'assign components to VField
    prjcomplete = VFlexDialog1.Get("prjcomplete")
    prjcomplete1 = VFlexDialog1.Get("prjcomplete1")
    chipQuantity = VFlexDialog1.Get("quantity")
    chpX = VFlexDialog1.Get("quantityx")
    chpavatar = VFlexDialog1.get("chpavatar")
    btnIcon = VFlexDialog1.Get("btnicon1")
    btnUpload = VFlexDialog1.Get("upload")
    btnIcon2 = VFlexDialog1.Get("btnIcon2")
    btnIcon3 = VFlexDialog1.Get("btnIcon3")
    btnImage1 = VFlexDialog1.Get("btnImage1")
    btnImage2 = VFlexDialog1.Get("btnImage2")
    icnWifi = VFlexDialog1.Get("wifi")
    icnWifi1 = VFlexDialog1.Get("wifi1")
    ano = VFlexDialog1.Get("ano")
    ano1 = VFlexDialog1.Get("ano1")
End Sub

Just like its done in the kitchen sink, to increment & decrement the minus /plus chip... we call..

B4X:
Private Sub quantity_Plus (e As BANanoEvent)
    chipQuantity.IncrementChip(page)
End Sub

Private Sub quantity_Minus (e As BANanoEvent)
    chipQuantity.DecrementChip(page)
End Sub

You can then do other actions depending on the value you get with .GetValue

We also have a youtube like upload button, same as in the kitchen sink.



This kind of file-input is made of a button, so to ensure that our file upload works, when the click event of the button is fired, the hidden file-input inside that button, should fire the change event. So, using the component name..

B4X:
'show the file selector
Private Sub upload_Button_Click (e As BANanoEvent)
    '*** NB: also generate a file change event for this component
    page.refs = vuetify.GetRefs
    btnUpload.Click
End Sub

This then fires the change event, which activates the File Selector and we can upload our fil to the server.

B4X:
Sub upload_change(fileObj As Map)
    page.refs = vuetify.GetRefs
    'has the file been specified
    If banano.IsNull(fileObj) Or banano.IsUndefined(fileObj) Then Return
    'show the loading indicator
    btnUpload.UpdateLoading(page, True)
    'get file details
    Dim fileDet As FileObject
    fileDet = BANanoShared.GetFileDetails(fileObj)
    'get the file name
    Dim fn As String = fileDet.FileName
    'you can check the size here
    Dim fs As Long = fileDet.FileSize
    If fs >= 5000 Then
    End If
    'start uploading the file to assets folder
    fileDet = BANanoShared.UploadFileWait(fileObj)
    'get the status of the upload
    Dim sstatus As String = fileDet.Status
    Select Case sstatus
        Case "error"
            'hide the uploader
            btnUpload.UpdateLoading(page, False)
            vuetify.ShowSnackBarError($"The file '${fn}' was not uploaded successfully!"$)
        Case "success"
            vuetify.ShowSnackBarSuccess($"The file '${fn}' was uploaded successfully!"$)
    End Select
    'get the upload full path
    Dim fp As String = fileDet.FullPath
    'update state of some element like an image
    'for vfield use SetValue
    'vimage.SetValue(page, fp)
    'vimage.SetImage(page, fp)
    'hide the uploading status
    btnUpload.UpdateLoading(page, False)
    'clear the file input
    btnUpload.Clear
End Sub
 

Mashiane

Expert
Licensed User
Longtime User
Example 4



This example looks at the auto-complete customizations and a few text fields.

Again, the code is fairly straight forward.

B4X:
Sub CreateVFlexDialog1
    VFlexDialog1.Clear
    Dim options As Map = CreateMap()
    options.Put("a", "A")
    options.Put("b", "B")
    options.Put("c", "C")
    '
    Dim options1 As Map = CreateMap()
    options1.Put("a", "A")
    options1.Put("b", "B")
    options1.Put("c", "C")
    
    VFlexDialog1.AddComboBox(1, 1, "test1", "Combo", options, "a", False)
    '
    VFlexDialog1.AddSelect(1, 2, "test2", "Select", options1, "b", False)
    '
    VFlexDialog1.AddCountryList(2, 1, "countries", "Country List", "")
    '
    VFlexDialog1.AddCurrencyList(2, 2, "currencies", "Currency List", "")
    '
    Dim options2 As Map = CreateMap()
    options2.Put("red", "Behind Schedule")
    options2.Put("orange", "In Progress")
    options2.Put("green", "Ahead of Schedule")
    VFlexDialog1.AddCustomColors(3, 1, "colors", "Custom Colors", options2, "green")
    '
    VFlexDialog1.AddGenderList(3, 2, "gender", "Gender", "male")
    '
    VFlexDialog1.AddWebsite(4, 1, "website", "Website", "http://www.b4x.com")
    '
    VFlexDialog1.AddEmail(4, 2, "email", "Email", "b4j@b4x.com")
    '
    VFlexDialog1.AddMoney(5, 1, "money", "Money", "1,234.56")
    '
    VFlexDialog1.AddThousands(5, 2, "thousands", "Thousands", "123,345")
    '
    VFlexDialog1.AddPhonesList(6, 1, "phones", "Dialing Codes", "")
    '
    VFlexDialog1.AddPersonsList(6, 2, "persons", "Persons List", "")
    '
    VFlexDialog1.AddRAG_Circles(7, 1, "ragc", "RAG Circles", "")
    VFlexDialog1.AddRAG_Smiles(7, 2, "rags", "RAG Smile", "")
    VFlexDialog1.AddRAG_UpDown(7, 3, "ragud", "RAG Up/Down", "")
    VFlexDialog1.AddReactions(7, 4, "react", "Reactions", "")
    '
    VFlexDialog1.Refresh
    '
    colors = VFlexDialog1.Get("colors")
    test1 = VFlexDialog1.Get("test1")
    test2 = VFlexDialog1.Get("test2")
    countries = VFlexDialog1.Get("countries")
    currencies = VFlexDialog1.Get("currencies")
    website = VFlexDialog1.Get("website")
    email = VFlexDialog1.Get("email")
    money = VFlexDialog1.Get("money")
    thousands = VFlexDialog1.Get("thousands")
    gender = VFlexDialog1.Get("gender")
    phones = VFlexDialog1.Get("phones")
    persons = VFlexDialog1.Get("persons")
    '
    ragc = VFlexDialog1.Get("ragc")
    rags = VFlexDialog1.Get("rags")
    ragud = VFlexDialog1.Get("ragud")
    react = VFlexDialog1.Get("react")
End Sub

We have also ensured that when you use the b4x intellisense, you get all the help you need, with the "Copy Code" feature...



Yes, the contents of any VField, can be read and set at runtime, for example, for the Persons list, we load the list on Mounted.

B4X:
Sub mounted        'ignoreDeadCode
    persons.ClearItems(page)
    persons.AddPerson(page, "anele", "Anele Mbanga", "./assets/myself.jpg")
    persons.AddPerson(page, "person1", "Person 1", "./assets/img5.png")
    persons.AddPerson(page, "person2", "Person 2", "./assets/img8.png")
    '
    vuetify.Loading(False)
End Sub
 

Mashiane

Expert
Licensed User
Longtime User
We are just busy putting the final screws and nuts into this. It is the recommended way to create forms in BVAD3 outside the Abstract Designer.

Watch this space!

PS: You can just skim through the code if interested, it wont run as BVAD3 is not updated as yet.

Later!
 

Attachments

  • BVAD3VFlexDialog.zip
    74.4 KB · Views: 282

Mashiane

Expert
Licensed User
Longtime User
Example 5





In this example we look at the DateTimePicker, append & append-inner icons, prepend & prepend-inner icons.
We also create an anchor and a FAB on the same form.

The prepend & append icons have click events. For example for the password, you click the "eye" to toggle the text field data-type.

As you will note, the first item is set just to fit the first half of the screen.

We have indicated here that

B4X:
VFlexDialog1.AddDateTime(1, 1, "datetime1", "Meeting Time", BANanoShared.DateTimeNow).SetItemDeviceSizes("datetime1", "12", "6", "6", "6")

For the mobile device, let it span 12 spaces and for medium to xtra large it should span 6 spaces.

The complete code is

B4X:
Sub BuildFlexDialog
    VFlexDialog1.Clear
    VFlexDialog1.AddDateTime(1, 1, "datetime1", "Meeting Time", BANanoShared.DateTimeNow).SetItemDeviceSizes("datetime1", "12", "6", "6", "6")
    VFlexDialog1.AddTextField(2, 1, "username", "User Name", "").SetItemPrependIcons("username", "mdi-account-badge", "")
    VFlexDialog1.AddTextField(2, 2, "username1", "User Name", "").SetItemPrependIcons("username1", "", "mdi-account-badge")
    VFlexDialog1.AddTextField(3, 1, "email", "Email Address", "").SetItemAppendIcons("email", "mdi-email-check-outline", "")
    VFlexDialog1.AddTextField(3, 2, "email1", "Email Address", "").SetItemAppendIcons("email1", "", "mdi-email-check-outline")
    VFlexDialog1.AddLink(4, 1, "lblAnchor", "b4x.com", "http://www.b4x.com", "_blank")
    VFlexDialog1.AddFAB(4, 2, "btnFab", "mdi-filmstrip", True, False).SetItemColors("btnFab", "blue", "normal", "white", "normal")
    '
    VFlexDialog1.Refresh
    datetime1 = VFlexDialog1.Get("datetime1")
    username = VFlexDialog1.Get("username")
    username1 = VFlexDialog1.Get("username1")
    email = VFlexDialog1.Get("email")
    email1 = VFlexDialog1.Get("email1")
    lblAnchor = VFlexDialog1.Get("lblAnchor")
    btnFab = VFlexDialog1.Get("btnFab")
End Sub

 

Mashiane

Expert
Licensed User
Longtime User
Update:

The kitchen sink webapp will now feature 5 pages that deal with how to create input dialogs using VFlexDialog. This has been placed just under the What's New section.



IMPORTANT: Using .Get(???) after .Refresh is now bypassable. This has meant that the chaining after the .Add??? calls will not work for the VFlexDialog as now the .Add?? calls return a VField.

You can now create inputs by just calling..

B4X:
Sub BuildFlexDialog
    datetime1 = VFlexDialog1.AddDateTime(1, 1, "datetime1", "Meeting Time", BANanoShared.DateTimeNow)
    VFlexDialog1.SetItemDeviceSizes("datetime1", "12", "6", "6", "6")
    '
    username = VFlexDialog1.AddTextField(2, 1, "username", "User Name", "")
    VFlexDialog1.SetItemPrependIcons("username", "mdi-account-badge", "")
    '
    username1 = VFlexDialog1.AddTextField(2, 2, "username1", "User Name", "")
    VFlexDialog1.SetItemPrependIcons("username1", "", "mdi-account-badge")
    '
    email = VFlexDialog1.AddTextField(3, 1, "email", "Email Address", "")
    VFlexDialog1.SetItemAppendIcons("email", "mdi-email-check-outline", "")
    '
    email1 = VFlexDialog1.AddTextField(3, 2, "email1", "Email Address", "")
    VFlexDialog1.SetItemAppendIcons("email1", "", "mdi-email-check-outline")
    '
    lblAnchor = VFlexDialog1.AddLink(4, 1, "lblAnchor", "b4x.com", "http://www.b4x.com", "_blank")
    '
    btnFab = VFlexDialog1.AddFAB(4, 2, "btnFab", "mdi-filmstrip", True, False)
    VFlexDialog1.SetItemColors("btnFab", "blue", "normal", "white", "normal")
    
    btnSpeedDial = VFlexDialog1.AddSpeedDial(5, 1, "btnSpeedDial", "mdi-close", "mdi-television-speaker", "left", True, False, False, True, vuetify.POSITION_TR)
    '
    VFlexDialog1.Refresh
End Sub
 
Cookies are required to use this site. You must accept them to continue using the site. Learn more…