B4J Tutorial [BANano]: Creating a CRUD app with LocalStorage Backend

Discussion in 'B4J Tutorials' started by Mashiane, Jan 26, 2019.

  1. Mashiane

    Mashiane Expert Licensed User

    Hi there

    27 March 2019 Update: Now updated to use version 1.06 of UOEBANano.

    Download UOEBANano
    Download SD Zip Library
    Download ATM Source Code

    #WithBANanoYouCan

    First, I am not an expert in BANano, this is just based on my experiments using it, after having asked a lot and lot of questions (and still do) about it. It's a very maverlous framework to work with and all my questions have been marked [SOLVED]. That kind of support from the developer is priceless.

    Anyway, I'm exploring CRUD, case in point use of LocalStorage. There is nothing cut in stone, so this is just my implementation. It has bugs and some irritations that I need to solve too but so far it works. Im still implementing more things, for example, I want to use the built in BANAno email sending functionality to send the localstorage contents to an email address for 'backup' purposes.

    So this is how the UX looks like and I will try and explain how I managed to get this real-world app working (bugs included, :)).

    BANano's engine is the glue that drives ones UX to works, now creating this UX was the hard part. Using BANano is becoming easier and easier (personal opinion). I'm realising though that some lack of knowledge about how this HTML5 thing works hinders progress. So knowing CSS and the whole DOM tree stuff will help one out. So much to learn.

    The app looks like this, there is nothing fancy about it and besides the client wanted it to be as simple as possible, add members, list members, search members. Pure basic stuff.

    memberlisting.png

    As noted abobe, the app has a navigation bar and buttons, a floating action button, a table with edit and delete, it also has a drawer...

    sidebar.png

    And some modal dialogs with back and next...

    modal1.png

    Member details 2

    member2.png

    Member Details 3

    member3.png

    One should be able to delete a member, we have a confirm dialog.

    confirmdelete.png

    And one should be able to search too..

    membersearch.png

    Yes, there are a lot of things I can improve and do differently, but hey, technology can be confusing.

    So let's create this SPA.

     
    Last edited: Mar 26, 2019
    amaxco, asales, Don Oso and 2 others like this.
  2. Mashiane

    Mashiane Expert Licensed User

    Creating the app...

    1. You need BANano (Compulsory)
    2. You need UOEBANano - this is a UX library I have created for the community. It's open source, has some bugs and irritations. You can use any UX framework of your choice anyway. That's the nice thing about BANano, it does not have a UX dependency. Such brilliance!!
    3. UOEBANano comes uncompiled, you need to compile it to a lib yourself in B4J.

    Ok, let's do this...

    1. Create a new UX project in B4J
    2. Reference BANano and UOEBanano libraries
    3. Add a new code module, let's call it pgIndex.

    Main

    Code:
    Sub Process_Globals
        
    Public BANano As BANano 'ignore
        Public App As UOEApp
        
    Public AppName As String = "ATM"
    End Sub

    Sub AppStart (Form1 As Form, Args() As String)
        
    'initialize banano for first use
        BANano.Initialize(AppName, AppName,1)
        
    'let us not use service workers
        BANano.UseServiceWorker = False
        BANano.HTML_NAME = 
    "index.html"
        BANano.Header.Title = 
    "ATM"
        
    'add needed files
        'loop through each file and add it
        modUOE.PrepareResources
        
    Dim fTot As Int = modUOE.Resources.Size - 1
        
    Dim fCnt As Int
        
    For fCnt = 0 To fTot
            
    Dim sFile As String = modUOE.Resources.GetValueAt(fCnt)
            
    If sFile.EndsWith(".css"Then
                BANano.Header.addcssfile(sFile)
            
    else if sFile.EndsWith(".js"Then
                BANano.Header.AddJavascriptFile(sFile)
            
    End If
        
    Next
        
    'ensure we have the latest version of the file
        '#If RELEASE
        'Dim TimeStamp As Long = DateTime.now
        'BANano.JAVASCRIPT_NAME =  $"app${TimeStamp}.js"$
        '#End If
        BANano.Build(File.DirApp)
        
    ExitApplication
    End Sub

    'Return true to allow the default exceptions handler to handle the uncaught exception.
    Sub Application_Error (Error As Exception, StackTrace As StringAs Boolean
        
    Return True
    End Sub

    ' HERE STARTS YOUR APP
    Sub ATM_Ready()
        App.Initialize(AppName,
    "white.black")
        App.Copyrights = 
    $"2019 ${AppName}, All Rights Reserved, powered by TGIF Zone Inc"$
        App.TermsAndConditionsURL = 
    ""
        App.DisclaimerURL = 
    ""
        App.PrivacyPolicyURL = 
    ""
        
    'lets open the first page
        pgIndex.Init(App)
    End Sub
    I'm sure by now we all know what should be in the Main module. You can leave the event of your app to be BANano so that you use BANano_Ready.

    UOEBANano comes up with some css and js files. At the moment its kinda loaded as I am still experimenting so don't be alarmed. I have created a map of all needed css and js files in modUOE for a basic project. So what happens in AppStart is that that man is transversed and the js and css files are added to the BANano.Header. So you can tweak ahead, besides we are experimenting and learning...

    In ATM_Ready, we just initialize our app with a few things. There is nothing much expected from there as it does not do much. If you have a terms and conditions, disclaimer and privacy policy URLS then you can just write the paths there. These are put on the UOECopyrights component just below the footer of the page. This you can turn off too.
     
    asales and joulongleu like this.
  3. Mashiane

    Mashiane Expert Licensed User

    But then what are these UOEBANano.Resources?

    Taking a peek at UOEBANano.modUOE, the resource list is...

    Code:
    Sub PrepareResources
        Resources.Initialize
        Resources.clear
        AddResource(
    "materialfont.css")
        AddResource(
    "materialize.min.css")
        AddResource(
    "preloader.css")
        AddResource(
    "jquery-3.3.1.min.js")
        AddResource(
    "jquery.timeago.min.js")
        AddResource(
    "materialize.min.js")
        
    '*** add other css and js files after this
        AddResource("all.min.css")
        AddResource(
    "all.min.js")
        AddResource(
    "nouislider.css")
        AddResource(
    "nouislider.min.js")
        AddResource(
    "jquery.sweet-modal.min.css")
        AddResource(
    "jquery.sweet-modal.min.js")
        
    'can save elements
        AddResource("Blob.min.js")
        AddResource(
    "canvas-toBlob.js")
        AddResource(
    "dom-to-image.min.js")
        AddResource(
    "FileSaver.min.js")
        AddResource(
    "printThis.js")
        
    'toolbar
        AddResource("jquery.toolbar.css")
        AddResource(
    "jquery.toolbar.min.js")
        
    'billboard chart
        AddResource("billboard.min.css")
        AddResource(
    "billboard.pkgd.min.js")
        
    'export
        AddResource("tableHTMLExport.js")
    End Sub

    Sub AddResource(sFile As String)
        Resources.Put(sFile,sFile)
    End Sub
    Besides the required css and js files for the UX lib, I use sweetmodal for confirm and alert boxes, Im experimenting with saving elements do images and also printing elements directly, there is a toolbar component I'm curious about and will definately add the D3 based charting framework in some future. So there is it.

    Ok, lets continue...
     
    asales and joulongleu like this.
  4. Mashiane

    Mashiane Expert Licensed User

    pgIndex.Header

    In Main.ATM_Ready we call pgIndex.Init, thats the code that builds the UX page as it is, adding modals, alerts, toasts, floating action buttons etc. The methodolody of the UOEBANano library is:

    1. Create your UX.
    2. Link events to elements using BANano
    3. No abstract designer elements (you code the ux)

    We need 3 modals for this exercise and a table. As these will be accessible anywhere on the page, lets define them as global.

    Code:
    'Static code module
    Sub Process_Globals
        
    Public BANano As BANano 'ignore
        Public App As UOEApp
        
    Public Page As UOEPage
        
    Private member As UOEModal
        
    Private member2 As UOEModal
        
    Private member3 As UOEModal
        
    Private tbl As UOETable
        
    Private action As String
    End Sub
    The next stages will be a bit by bit process.

    Let's create the navigation bar and drawer

    Navigation Bar

    The navigation bar has a drawer and some buttons..

    navbar.png


    Code:
    'create the page
    Sub Init(thisApp As UOEApp)
        App = thisApp
        
    'initialize the page, centered logo
        Page.Initialize(App,"pg1",False,"ATM Membership App",App.EnumLogoPosition.center,True,False,"","")
        
    Page.NavBar.AddImageL("logo","assets/logo.png",True,"40","40","1")
        
    Page.NavBar.AddIcon("search","mdi-search","#",False,App.EnumAlignment.right, App.EnumVisibility.visible, "")
        
    Page.NavBar.AddIcon("refresh","mdi-autorenew","#",False,App.EnumAlignment.right, App.EnumVisibility.visible, "")
    There are a could of things that happen here. Whilst creating the navigation bar, we also indicate that the page container should not be centered. We have a logo that is centered. A logo can be aligned left, center or right.

    We add an image to the left of the navigation bar and specify its aspects with AddImageL, we add two icons for search and refresh to the right of the navigation. When search is clicked, a search modal should appear and when refresh is clicked, we want the table contents to be refreshed.

    To handle all the events of the page, we have defined a single event handler and parse the event id to get what we need to do. This is called indexEvents.

    Code:
    Sub indexEvents(event As BANanoEvent)
        
    'stop bubbling of events
        event.StopPropagation
        
    'get the event id
        Dim thisEvent As String = event.ID
        
    Log(thisEvent)
        
    Dim ePrefix As String = App.MvField(thisEvent,1,"-")
        
    Select Case ePrefix
            
    Case "search"
                SearchMembers
            
    Case "searchmember"
                SearchMembers
            
    Case "refreshmember"
                CreateTable
     
    asales and joulongleu like this.
  5. Mashiane

    Mashiane Expert Licensed User

    Creating the drawer

    On small devices, the navigation bar buttons usually hide themselves, providing a drawer for the same functionality is better.

    drawer.png

    As noted, we have functionality to add a member, search members and refresh. The back up functionality is still a work in progress and will use the BANAno built in functionality to send an email of the local storage contents.

    Let's look at the code...

    Code:
    Page.NavBar.Drawer.DrawerWidth = 350
        
    'add the userview
        Page.NavBar.Drawer.AddUserView("ATM","mbangasalat@gmail.com","assets/logo.png","assets/background.png")
        
    Page.NavBar.Drawer.AddHeader("addmember","mdi-add_circle_outline","Add Member","",False,"","","green.transparent",True)
        
    Page.NavBar.Drawer.AddHeader("searchmember","mdi-search","Search Members","",False,"","","",True)
        
    Page.NavBar.Drawer.AddHeader("refreshmember","mdi-autorenew","Refresh","",False,"","","",True)
        
    Page.NavBar.Drawer.AddHeader("backup","mdi-backup","Back Up","",False,"","","",True)
    Automatically, as these are clickable things, event handlers will be added when the page is created by UOEBANano, thus creating the BANano HandleEvents functionality. As each element in a page needs to have its own unique element id, as much as search, add, refresh do the same things as per navigation bar, these need to have different ids. Having the same id also could create multiple event handlers.
     
    asales and joulongleu like this.
  6. Mashiane

    Mashiane Expert Licensed User

    Creating the footer

    footer.png

    This footer has a copyright at the bottom. A footer can be visible and not visible on a page. A footer just like Page.Content is just a container that you can add rows and columns to. So here we tell the Page that it needs to have copyrights, we need to fix the footer, the container that will house elements in the footer should not be centered.

    We then add rows to the footer and then add a paragraph to it. The copyrights details you will remember were defined in Main.ATM_Ready.

    Code:
    'footer
        Page.HasCopyright = True
        
    Page.FixedFooter = True
        
    Page.HasFooter = True
        
    Page.Footer.CenterInPage = False
        
    Page.footer.AddRowsM(1,0,0"""").AddColumnsSP(1,12,12,12,10,10"""")
        
    Page.Footer.AddParagraph(1,1,"","Developed by Mashy, anele@mbangas.com","","")
        
    'add a tooltip
        Page.AddToolTip("newmember","Add a new member",App.EnumTooltipPos.Left,50)
        
    'add fab
        Page.Content.AddFab1("newmember","","mdi-add","","white.green","pulse")
    We also want to provide functionality for a user to select an action button that will enable one to add new members. We do so with AddFab1
    When a user hovers over the fixed action button, we want a tooltip to be shown, thus we add the tooltip BEFORE we add the component on the page. Because this button is important to add members for the app, we want it to pulse, this adding the "pulse" class to it.

    There are some themes that are built inside UOEBANAno that you can use. Although the theming for UOEBANano is imcomplete, for basic things like buttons etc, it seems to work well. Also the event handler for this FAB is created when the Page.Create method is being executed, AFTER all the elements of the page are added to the page grid. The universal event handler, indexEvents is being used.
     
    Johan Hormaza, asales and joulongleu like this.
  7. Mashiane

    Mashiane Expert Licensed User

    Creating the page grid to house components.

    Code:
    'define grid for the page
        Page.Content.AddRows(2,"""").AddColumnsOSMP(1,0,0,0,12,12,12,0,0,20,20,"","center-align")
        
    Page.Content.AddRows(5,"""").AddColumnsOSMP(1,0,0,0,12,12,12,0,0,20,20,"","")
        
    'add other page contents
        Dim lbl As UOELabel
        lbl.Initialize(App,
    "lblHead",App.EnumLabelSize.span,"","","")
        lbl.AddColor(
    "{B}African{NBSP}{/B}",App.EnumColor.BLACK)
        lbl.AddColor(
    "{B}Transformation{NBSP}{/B}",App.EnumColor.blue)
        lbl.AddColor(
    "{B}Movement{NBSP}{/B}",App.EnumColor.green)
        
    Page.Content.AddLabel(2,1,lbl)
        
        
    'Page.content.AddImage1(3,1,"mainlogo","assets/mainlogo.png","Main Logo",False,App.EnumZDepth.zdepth_2,True,True,False,"300","500",App.EnumAlignment.CenterDiv,"")
        
        
        
        Member1Modal
        Member2Modal
        Member3Modal
        
    'create the boxy
        Page.Create
        
    'add a table of contents: this should update already existing content
        CreateTable
        action = 
    "create"
        
    'update settings so that labels of group are set properly.
        member.Content.setColumnPadding1(1,1,0,0,10,0)
        member2.Content.setColumnPadding1(
    2,1,0,0,10,0)
    End Sub
    If you have used ABM before the AddRows and AddColumnOSMP methods should not be a surprise. The same methodolody has been followed, thus ensuring a smooth transition to this as possible. It is however true that UOEBANano is not affiliated or supported by ABM and UOEBANano is NOT ABM, but indeed is inspired by ABM, by the community for the community. Its open source, meaning its yours too. ;)

    Anyway, in most components that use the Container, these have been defined as .Content.

    At R2C1 we add a label with some colors. BANano has a .SF function to do that, this is just my flavour of doing things. We add 3 modals to the page, execute a Page.Create which generates all the HTML needed for the elements and then inject the table with CreateTable and then padd some modal sheet elements. We do these after the page is created because such padding used BANAno.<>.SetStyle methods.

    CreatePage creates the table and injects data to it from LocalStorage so each time it runs, it replaces an html element on the page RC, thus also created after the page is created because it used BANano.SetHTML methods

    The most important call after defining your components is Page.Create. After it, usually one has to link events to the elements with BindEvents but because the table is dynamic and has its own events (edit & delete), I am only binding events after the table is created.
     
    asales and joulongleu like this.
  8. Mashiane

    Mashiane Expert Licensed User

    Lets look at the modal definitions

    MemberModal1, MemberModal2 and MemberModal3

    These subs create the modal sheets that the membership details should be captured in. As the code for these modals is closely related and the same, only 1 will be discussed here..

    MemberModal1

    Code:
    Sub Member1Modal
        
    '***
        member.Initialize(App,"aemember","")
        member.fixedFooter = 
    True
        member.dismissible = 
    False
        
    'process the header
        member.Header.AddRows(1,"white.lightblue","").AddColumnsOSMP(1,0,0,0,2,2,2,10,0,10,10,"","").AddColumnsOSMP(1,0,0,0,10,10,10,10,0,10,10,"","")
        member.Header.AddImage1(
    1,1,"","assets/logo.png","logo",True,App.EnumZDepth.zdepth_3,True,True,False,"50px","50px","",""
        member.Header.AddH4(
    1,2,"h4","Member [1/3]","","")
        member.Content.AddRowsM(
    3,5,5,"","").AddColumnsOSMP(2,0,0,0,12,6,6,0,10,0,0,"","")
        
    'category
        Dim category As UOERadioGroup
        category.Initialize(App,
    "category","Category of Membership",False,"")
        category.AddItem(
    "itemnew","N","New",True,True,True,True)
        category.AddItem(
    "itemrenewal","R","Renewal",True,True,True,False)
        member.Content.AddRadioGroup(
    1,2,category)
        member.Content.AddParagraph(
    1,1,"","Category of Membership","","")
        
    'add title
        member.Content.AddTextBox(2,1,"title","","Title","","",0,"","")
        
    'add surname
        member.Content.AddTextBox(2,2,"surname","","Surname","","",0,"","")
        
    'add fullnames
        member.Content.AddTextBox(3,1,"fullnames","","Full Name(s)","","",0,"","")
        
    'add id number
        member.Content.AddTelephone(3,2,"idnumber","","Identity Number","","",0,"","")
        
    'update fields to use in updates
        Dim flds As List = member.fields
        flds.Initialize
        flds.clear
        flds.AddAll(
    Array As String("category""title""surname""fullnames""idnumber"))
        
    'add footer
        member.Footer.AddRows(1,"","").AddColumns12(1,"","")
        
    'add footer buttons
        member.Footer.AddRaisedButton(1,1,"btnCancelMember","Cancel","","","white.red","")
        member.Footer.AddRaisedButton(
    1,1,"btnSaveMember1","Next","","","white.green","")
        
    Page.Content.AddModal(member)
        
    End Sub
    In globals we defined member as an UOEModal component. What we do in this sub is just ensure that the needed components are existing.
    The modal should have a fixed footer and cannot be dismissed. We create a header, content and footer with some contents for the UX.

    The header, content and footer of the modal are instances of UOEContainer just like Page.Content is an instance of UOEContainer. Where everything happens.

    1. We add a grid in the header and add a logo and a heading.
    2. We add a grid to the content too and then add some elements to the modal.content, e.g. radiogroup, textboxes etc.
    3. Also on the footer, we add a grid and then add the save and cancel buttons for Cancel and Next.

    Clearing the modal contents

    Clearing modal contents for all three modals are ClearModal1, ClearModal2 and ClearModal3 respectively. For the first modal..

    Code:
    'clear the contents of the modal
    Sub ClearMember1
        
    Page.setTextBox("title","")
        
    Page.setTextBox("surname","")
        
    Page.setTextBox("fullnames","")
        
    Page.setTextBox("idnumber","")
        
    Page.SetRadio("category""N")
    End Sub
    Getting the modal contents

    Getting the modal contents for all three modals are GetMember1, GetMember2 and GetMember3 respectively. For each modal, the element details are read and validated, if anything required is missing a 'red' toast message is provided to nudge the end user. When all the details are read, the details are saved on a temporal map object called 'member' in local storage. This 'member' item is updated each time a user navigates between the screen with Back and Next.

    Code:
    'get saved details, check validity
    Sub GetMember1 As Boolean
        
    Dim scategory As String = Page.GetRadio("category")
        
    Dim stitle As String = Page.GetTextbox("title")
        
    Dim ssurname As String = Page.GetTextbox("surname")
        
    Dim sfullname As String = Page.GetTextbox("fullnames")
        
    Dim sidnumber As String = Page.GetTextbox("idnumber")
        
    'tell user if blank
        If scategory <> "N" Then
            
    If scategory <> "R" Then
                
    Page.ToastError("The category has not been selected, please select a category!")
                
    Return False
            
    End If
        
    End If
        
        
    If stitle.Length = 0 Then
            
    Page.ToastError("The title cannot be blank, please specify a title!")
            
    Return False
        
    End If
        
    If ssurname.Length = 0 Then
            
    Page.ToastError("The surname cannot be blank, please specify a surname!")
            
    Return False
        
    End If
        
    If sfullname.Length = 0 Then
            
    Page.ToastError("The fullnames cannot be blank, please specify the fullnames!")
            
    Return False
        
    End If
        
    If sidnumber.Length = 0 Then
            
    Page.ToastError("The id number cannot be blank, please specify an id number!")
            
    Return False
        
    End If
        
        
    'get saved member
        Dim sMember As String = Page.GetLocalStorage("member")
        
    If sMember = "" Then
            
    Dim m1 As Map = CreateMap("category" : scategory, "title" : stitle, "surname" : ssurname, "fullnames" : sfullname, "idnumber" : sidnumber)
            sMember = App.Map2Json(m1)
        
    Else
            
    Dim m1 As Map = App.Json2Map(sMember)
            m1.Put(
    "category", scategory)
            m1.Put(
    "title", stitle)
            m1.Put(
    "surname", ssurname)
            m1.Put(
    "fullnames", sfullname)
            m1.Put(
    "idnumber", sidnumber)
            sMember = App.Map2Json(m1)
        
    End If   
        
    'save to localstorage
        BANano.SetLocalStorage("member", sMember)
        
    Return True
    End Sub
    Setting the modal contents

    Setting the modal contents happens when a member details are read from the temporal 'member' local storage object and then displayed to the screen. The member object is parsed and then each element shown where its supposed.

    The setters for all three modals are ReadMember1, ReadMember2 and ReadMember3 respectively for each modal.

    Code:
    Sub ReadMember1(strMember As String)
        
    If strMember = "" Then Return
        
    'convert the member to a map
        Dim mm As Map = App.Json2Map(strMember)
        
    Dim stitle As String = mm.Get("title")
        
    Dim ssurname As String = mm.Get("surname")
        
    Dim sfullnames As String = mm.Get("fullnames")
        
    Dim sidnumber As String = mm.Get("idnumber")
        
    Dim scategory As String = mm.Get("category")
        
        
    Page.SetRadio("category", scategory)
        
    Page.setTextbox("title", stitle)
        
    Page.setTextbox("surname", ssurname)
        
    Page.setTextbox("fullnames", sfullnames)
        
    Page.setTextbox("idnumber", sidnumber)
    End Sub
    The 'member' object is saved to localstorage as a JSON object and has to be parses to get the various keys.
     
    joulongleu and asales like this.
  9. Mashiane

    Mashiane Expert Licensed User

    A look at the table. CreateTable

    The main universal method for the table is the sub called CreateTable. What this does is to create the table structure, read the contents from local storage of all saved members and then displays them on the table. When the table is created, then its injected into the page, AFTER all other components are already added and then events are linked to the page for all elements needing events and not defined internally by UOEBANano after Page.Create.

    Code:
    'refresh the table
    Sub CreateTable
        
    'table of members
        tbl.Initialize(App,"tblmembers","")
        tbl.CanExport = 
    False
        tbl.HasBorder = 
    True
        tbl.AddHeadingsFields(
    Array As String("category","surname","fullnames","idnumber","gender","cellphone","email","edit","delete"))
        tbl.AddHeadings(
    Array As String("Category","Surname","Fullnames","ID #","Gender","Cellphone #","Email","Edit","Delete"))
        tbl.AddHeadingsThemes(
    Array As String(App.theme,App.theme,App.theme,App.theme,App.theme,App.theme,App.theme,App.theme,App.theme))
        tbl.AddHeadingsAlignment(
    Array As String("","","","","","","",App.EnumAlignment.Center,App.EnumAlignment.Center))
        tbl.AddHeadingsWidths(
    Array As String("","","","","","","","60","60"))
        
    'load saved records from localstorage
        Dim mCnt As Int =  0
        
    'get the field names from the table, our records as json format
        Dim flds As List = tbl.Fields
        
    'read the saved members from localstorage
        Dim strMembers As String = Page.GetLocalStorage("members")
        
    If strMembers = "" Then
        
    Else
            
    'convert existing members to a list
            Dim lstM As List = App.Json2List(strMembers)
            
    'each member is a json string
            For Each strMember As String In lstM
                
    'each member record is a json string, convert this to a map
                Dim memberM As Map = App.Json2Map(strMember)
                
    'the id number is the key
                Dim sidnumber As String = memberM.Get("idnumber")
                
    'extract the values needed
                Dim memberR As List
                memberR.Initialize
                memberR.clear
                
    'loop through each fld
                Dim fldTot As Int = flds.Size - 1
                
    Dim fldCnt As Int
                
    For fldCnt = 0 To fldTot
                    
    Dim fldStr As String = flds.Get(fldCnt)
                    
    Dim hasFld As Boolean = memberM.ContainsKey(fldStr)
                    
    'only process the fields we want to show on the table
                    If hasFld Then
                        
    Dim fldVal As String = memberM.Get(fldStr)
                        memberR.Add(fldVal)
                    
    End If
                
    Next
                
    'add the edit button, get their settings first to theme the buttons
                Dim hdrEdt As UOETableHeading = tbl.getTableHeading("edit")
                
    Dim hdrDel As UOETableHeading = tbl.getTableHeading("delete")
                
    'define the buttons, the id is the key for each record, include it for each button
                Dim btnEdt As UOEButton
                
    Dim eKey As String = $"editrow-${mCnt}-${sidnumber}"$
                
    Dim dKey As String = $"deleterow-${mCnt}-${sidnumber}"$
                
    'define a floating button for edit/delete
                btnEdt = Page.Content.getFloatingButton(eKey,"mdi-create",hdrEdt.Theme,hdrEdt.ClassName)
                
    Dim btnDel As UOEButton
                btnDel = 
    Page.Content.getFloatingButton(dKey,"mdi-delete_forever",hdrDel.Theme,hdrDel.ClassName)
                
    'add buttons to record
                memberR.Add(btnEdt.ToString)
                memberR.Add(btnDel.ToString)
                
    'add click events for edit / delete
                App.AddEvent(eKey, "click")
                App.AddEvent(dKey, 
    "click")
                
    'add row to table
                tbl.AddRow(memberR,Null,Null)
                
    'increment table row
                mCnt = mCnt + 1
            
    Next
        
    End If
        
    'update the container with the new table
        Page.Content.ReplaceRC(3,1,tbl.ToString)
        
    'relink the events for the page, if we were not running CreateTable, this should be added directly under page.create
        App.BindEvents("indexEvents", Me)
    End Sub
    As member records are saved as JSON objects in localstorage, each member has its own field definition. One can read some of this content to display on a table. To tell the table which fields to processes, we add fields to the table with AddHeadingFields. We would recommended this to be the first thing one does.

    Then the table heading titles are added with AddHeadings, then Themes, then Alignment and Widths. Then, we read the saved 'members' list from localstorage, loop through each one, determine which fields are displayed on the table as defined by AddHeadingFields and then create each member record as a List object. We read the theme for the edit / delete columns from their specified themes with AddHeadingThemes and apply the same theme for Edit/Delete buttons.

    Each edit/delete button is created per member being added using the member idnumber as a unique key, buy defining each button with .getFloatingButton. This just generates the required HTML for the button.

    When all the member details are added to the memberR list item, then each edit/delete is linked to an event with .AddEvent. AddEvent does nothing else other than just keeping a record of all the elements that need to be having events. After all the members are added to the page, events are bound to the controls with .BindEvents

    This is depicted like this..

    Code:
    'add an event to the collection
    Sub AddEvent(elID As String, elEvent As String)
        elID = elID.ToLowerCase
        elID = elID.Trim
        elEvent = elEvent.trim
        
    If elID.Length > 0 And elEvent.Length > 0 Then
            Events.Put(elID,elEvent)
            
    Dim em As List
            em.Initialize
            em.AddAll(
    Array As String("icon","img","badge","title","badge"))
            
    For Each strsuffix As String In em
                
    Dim strkey As String = $"${elID}-${strsuffix}"$
                Events.Put(strkey,elEvent)
            
    Next
        
    End If   
    End Sub
    Code:
    'bind the events
    Sub BindEvents(elMethod As String, EventHandler As Object)
        
    Dim mTot As Int = Events.Size - 1
        
    Dim mCnt As Int
        
    For mCnt = 0 To mTot
            
    Dim elID As String = Events.GetKeyAt(mCnt)
            
    Dim elEvent As String = Events.Get(elID)
            elID = elID.Trim
            elEvent = elEvent.trim
            
    If elID.Length > 0 And elEvent.Length > 0 Then
                
    Dim btnEvent As String = $"${elID}_${elEvent}"$
                
    Dim elKey As String = $"#${elID}"$
                
    'these call a universal method
                If elMethod.Length > 0 Then
                    btnEvent = elMethod.tolowercase
                
    End If
                
    If EventHandler <> Null Then
                    
    Dim elm As BANanoElement = BANano.GetElement(elKey)
                    elm.Off(elEvent)
                    elm.HandleEvents(elEvent, EventHandler, btnEvent)
                
    End If
            
    End If
        
    Next
    End Sub
    By default, all events defined like this in an UOEBANano project will use HandleEvents, but then you can turn an event off as done here.

     
    joulongleu and asales like this.
  10. Mashiane

    Mashiane Expert Licensed User

    Saving Member Details

    The third modal has a SAVE button whilst others have NEXT, that is where the save happens. When scrolling back and forth in the modals, one can change details etc. On each Next click, the member details are read and validated and then saved to a temporal local storage object named 'member'.

    The button linked to saving all member details is btnsavemember3, this executes the CreateUpdate sub.

    This all gets done inside the indexEvents sup.

    Code:
    Sub indexEvents(event As BANanoEvent)
        
    'stop bubbling of events
        event.StopPropagation
        
    'get the event id
        Dim thisEvent As String = event.ID
        
    Log(thisEvent)
        
    Dim ePrefix As String = App.MvField(thisEvent,1,"-")
        
    Select Case ePrefix
            
    Case "search"
                SearchMembers
            
    Case "searchmember"
                SearchMembers
            
    Case "refreshmember"
                CreateTable
            
    Case "refresh"
                
    'refresh the table
                CreateTable
            
    Case "search"
                
    'search for a member
            Case "editrow"
                
    Dim rowPos As String = App.mvfield(thisEvent,2,"-")
                
    Dim rowKey As String = App.MvField(thisEvent,3,"-")
                action = 
    "update"
                ClearMember1
                ClearMember2
                ClearMember3
                
    Dim strMember As String = ReadMember(rowKey)
                
    Page.SetLocalStorage("member", strMember)
                ReadMember1(strMember)
                ReadMember2(strMember)
                ReadMember3(strMember)
                
    'refresh controls
                Page.refresh
                member.Open
            
    Case "deleterow"
                
    Dim rowPos As String = App.mvfield(thisEvent,2,"-")
                
    Dim rowKey As String = App.MvField(thisEvent,3,"-")
                
    'ask the user
                'save this memberid for later retrieval
                Page.SetLocalStorage("memberid", rowKey)
                
    'save the table row for later retrieval
                Page.SetLocalStorage("index", rowPos)
                
    'call a method existing in this application
                '_banano_atm_pgindex.deletemember();
                Dim onYes As String = Page.CallMethod("atm","pgindex","deletemember();")
                
    Page.SweetModalConfirm($"Confirm Delete: ${rowKey}"$, _
                
    "Are you sure that you want to delete this record? You will not be able to undo your changes.", onYes, "")
                
            
    Case "btncancelmember3"
                member3.close
                member2.Open
            
    Case "btnsavemember3"
                
    Dim bDo As Boolean = GetMember3
                
    If bDo Then
                    CreateUpdate
                    action = 
    "create"
                    
    Page.ToastSuccess("Member updated succesfully!")
                    member3.close
                
    End If
            
    Case "btnsavemember2"
                
    Dim bDo As Boolean = GetMember2
                
    If bDo Then
                    
    'goto address
                    member2.close
                    member3.open
                
    End If
            
    Case "btncancelmember2"
                member2.Close
                member.open
            
    Case "btncancelmember"
                member.close
            
    Case "btnsavemember1"
                
    'a member is being saved
                Dim bDo As Boolean = GetMember1
                
    If bDo Then
                    member.close
                    member2.open
                
    End If
            
    Case "addmember"
                
    'add a new member, create the modal
                action = "create"
                ClearMember1
                ClearMember2
                ClearMember3
                
    Page.refresh
                member.Open
            
    Case "newmember"
                action = 
    "create"
                ClearMember1
                ClearMember2
                ClearMember3
                
    Page.refresh
                member.open
            
    Case "backup"
                
    'read members and send email of records
        End Select
    End Sub
    CreateUpdate sub, reads the temporal saved 'member' object from localstorage and also reads the existing 'members' list from localstorage. Using the members idnumber as a key, the existing members are compared and if a match of an idnumber is found, that particular record is updated, if not a new member record is added to the list. This list is then saved back to localstorage as a 'members' object, a list of JSON objects that are internally maps with each member record.

    Code:
    'update list of members
    Sub CreateUpdate
        
    Dim hasMembers As Boolean = False
        
    Dim lstM As List
        lstM.Initialize
        lstM.clear
        
    'get last member
        Dim smember As String = Page.GetLocalStorage("member")
        
    'get a list of members
        Dim lmember As String = Page.GetLocalStorage("members")
        
    'members exist, add to existing
        If lmember <> "" Then
            
    'there are existing members
            lstM = App.Json2List(lmember)
            hasMembers = 
    True
        
    End If
        
    If hasMembers Then
            
    'read the id of this member
            Dim thisMember As Map = App.Json2Map(smember)
            
    Dim sidnumber As String = thisMember.Get("idnumber")
            
    Dim mTot As Int = lstM.Size - 1
            
    Dim mCnt As Int
            
    Dim mFound As Boolean = False
            
    For mCnt = 0 To mTot
                
    Dim savedMember As String = lstM.Get(mCnt)
                
    Dim savedMemberM As Map = App.Json2Map(savedMember)
                
    Dim savedid As String = savedMemberM.Get("idnumber")
                
    If savedid = sidnumber Then
                    lstM.Set(mCnt,smember)
                    mFound = 
    True
                    
    Exit
                
    End If
            
    Next
            
    If mFound = False Then
                lstM.Add(smember)
            
    End If
        
    Else
            lstM.Add(smember)
        
    End If
        
    Dim allMembers As String = App.List2Json(lstM)
        
    Page.SetLocalStorage("members", allMembers)
        
    Page.SetLocalStorage("member""")
        
    'create the table
        CreateTable
    End Sub
     
    joulongleu likes this.
  11. Mashiane

    Mashiane Expert Licensed User

    Editing Member Details

    To edit details of a member, an edit button is provided per member, each edit button is linked to the idnumber of the member. When edit is clicked on a member, as that is linked to each member, we are able to pick up which member's id number was selected from the multivalue field linked to the event.

    The edit button is linked to a click event and then we linked the event on CreateTable per member row.

    Code:
    Case "editrow"
                
    Dim rowPos As String = App.mvfield(thisEvent,2,"-")
                
    Dim rowKey As String = App.MvField(thisEvent,3,"-")
                action = 
    "update"
                ClearMember1
                ClearMember2
                ClearMember3
                
    Dim strMember As String = ReadMember(rowKey)
                
    Page.SetLocalStorage("member", strMember)
                ReadMember1(strMember)
                ReadMember2(strMember)
                ReadMember3(strMember)
                
    'refresh controls
                Page.refresh
                member.Open
    So, what this does is, get the member idnumber from the eventid, clear all the modals from 1-3, read the member using ReadMember from local storage and then save the active member to the 'member' object in localstorage and then use the setter methods ReadMember1 - 3 and then displays the first modal using .Open.

    ReadMember

    This gets passed the idnumber to search for from already saved 'members' in localstorage. This loops through each record on the list, finds the one matching the idnumber specified and then returns it.

    Code:
    'read a member from localstorange
    Sub ReadMember(sid As StringAs String
        
    'get the existing members
        Dim strMembers As String = Page.GetLocalStorage("members")
        
    If strMembers = "" Then
            
    Return "{}"
        
    Else
            
    'convert existing members to a list
            Dim lstM As List = App.Json2List(strMembers)
            
    'each member is a json string
            Dim mTot As Int = lstM.Size - 1
            
    Dim mCnt As Int
            
    For mCnt = 0 To mTot
                
    Dim strMember As String = lstM.Get(mCnt)
                
    Dim memberM As Map = App.Json2Map(strMember)
                
    'the id number is the key
                Dim sidnumber As String = memberM.Get("idnumber")
                
    If sidnumber = sid Then
                    
    Return strMember
                
    End If
            
    Next
        
    End If
        
    Return "{}"
    End Sub
    Each time you update the member details are click Next, this 'member' object is updated in localstorage and when the last button is clicked to Save the member, CreateUpdate is ran to read the temporal 'member' local storage object and update the 'members' local storage object.
     
    Last edited: Jan 26, 2019
    joulongleu likes this.
  12. Mashiane

    Mashiane Expert Licensed User

    Deleting a Member

    To delete a member, one has to click the delete button close to the member they wish to delete. For each member row, this is linked to a button with an event id prefixed by delete row (remember on CreateTable)

    Code:
    Case "deleterow"
                
    Dim rowPos As String = App.mvfield(thisEvent,2,"-")
                
    Dim rowKey As String = App.MvField(thisEvent,3,"-")
                
    'ask the user
                'save this memberid for later retrieval
                Page.SetLocalStorage("memberid", rowKey)
                
    'save the table row for later retrieval
                Page.SetLocalStorage("index", rowPos)
                
    'call a method existing in this application
                '_banano_atm_pgindex.deletemember();
                Dim onYes As String = Page.CallMethod("atm","pgindex","deletemember();")
                
    Page.SweetModalConfirm($"Confirm Delete: ${rowKey}"$, _
                
    "Are you sure that you want to delete this record? You will not be able to undo your changes.", onYes, "")
    Clicking the delete button activates through BANano the linked event indexEvents method and then the eventid is parsed to execute delete row.

    The rowKey, being the idnumber of the member to be deleted is read from the event event, a prompt is provided to the user to confirm..

    dodelete.png

    using the idnumber of the member, when a user selects Yes, then the DeleteMember sub is called. The idnumber of the member to be deleted is saved as 'memberid' to localstorage and DeleteMember uses that to delete the member from the existing list of 'members' in localstorage.

    NB: There is a new way in BANano to execute this type of functionality. Havent got my head around it yet.

    Code:
    'user clicked yes on confirmation, process the deletion
    Sub DeleteMember
        
    'get the memberid to delete
        Dim memberid As String = Page.GetLocalStorage("memberid")
        
    'get the existing members
        Dim strMembers As String = Page.GetLocalStorage("members")
        
    If strMembers = "" Then
        
    Else
            
    'this will store all unmatching records
            Dim nList As List
            nList.Initialize
            nList.clear
            
    'convert existing members to a list
            Dim lstM As List = App.Json2List(strMembers)
            
    'each member is a json string
            Dim mTot As Int = lstM.Size - 1
            
    Dim mCnt As Int
            
    For mCnt = 0 To mTot
                
    Dim strMember As String = lstM.Get(mCnt)
                
    Dim memberM As Map = App.Json2Map(strMember)
                
    'the id number is the key
                Dim sidnumber As String = memberM.Get("idnumber")
                
    If sidnumber <> memberid Then
                    nList.Add(strMember)
                
    End If
            
    Next
            
    'convert the list and save it back
            strMembers = App.List2Json(nList)
            
    Page.setlocalstorage("members", strMembers)
            
    'create the table
            CreateTable
        
    End If   
    End Sub
    This basically loops through all the members in localstorage saved list, creates a new list of only the members that dont have the idnumber being deleted and saves the list back to localstorage.
     
    joulongleu likes this.
  13. Mashiane

    Mashiane Expert Licensed User

    Searching for a member:

    *Not yet implemented.

    Back Up

    *Not yet implemented

    NB: the drawer buttons acts the same way as the navigator buttons as linked in the event handler indexEvents.

    Enjoy, for the love of the community!

    Ta!
     
    joulongleu likes this.
  14. Mashiane

    Mashiane Expert Licensed User

    Compiling your BANano WebApp

    Now that you are finished with your UX design and functionality coding, its time to let the BANano framework take over. You need to compile the web app with BANano so that you can distribute it.

    Hit F5 or click the run button in the toolbar. This will compile your app to the specified directory. The generated content by BANano for File.DirApp will be the objects folder. You can open the index.html file on your browser and see the magic you created.

    BANano creates these folders, assets (everything in your Files tab) that is not .css and not .js, scripts - everything that is .js and styles - everything that is .css. Also added will be your html file, manifest.json.

    distribute.png

    All the best...
     
    joulongleu likes this.
  15. Mashiane

    Mashiane Expert Licensed User

    Coming Soon:

    [BANano] Creating a CRUD App with BANanoSQL Backend
     
    asales and joulongleu like this.
  16. Mashiane

    Mashiane Expert Licensed User

    27 March 2019 Update...

    The table has been updated to make it easier to work with.

    1. Add the table before Page.Create..

    Code:
    'add the table at r3c1
        tbl.Initialize(App,"tblmembers","","idnumber",True,True,False)
        tbl.CanExport = 
    False
        tbl.HasBorder = 
    True
        tbl.AddHeadingsFields(
    Array As String("membernumber","category","surname","fullnames","idnumber","gender","cellphone","email","edit","delete"))
        tbl.AddHeadings(
    Array As String("Member #","Category","Surname","Fullnames","ID #","Gender","Cellphone #","Email","Edit","Delete"))
        tbl.AddHeadingsThemes(
    Array As String(App.Theme,App.theme,App.theme,App.theme,App.theme,App.theme,App.theme,App.theme,App.theme,App.theme))
        tbl.AddHeadingsAlignment(
    Array As String("","","","","","","","",App.EnumAlignment.Center,App.EnumAlignment.Center))
        tbl.AddHeadingsWidths(
    Array As String("","","","","","","","","60","60"))
        
    Page.Content.AddTable(3,1,tbl)
    One can also add individual columns and specify each column properties. This is far more better than adding columns as per above way.

    2. Loading the table from records, called after Page.Create

    Code:
    'refresh the table
    Sub RefreshTable
        
    'read the saved members from localstorage
        Dim strMembers As String = Page.GetLocalStorage("members")
        
    If strMembers = "" Then Return
        
    'convert existing members to a list
        Dim lstm As List = App.Json2List(strMembers)
        
    'we need maps to process
        Dim lstX As List
        lstX.Initialize
        
    For Each strMember As String In lstm
            
    Dim mapx As Map = App.Json2Map(strMember)
            lstX.Add(mapx)
        
    Next
        tbl.Load(Me,
    "pgindex",lstX,"white.black")
    End Sub
    This reads all saved JSON records from members key in localstorage i.e. BANAno.GetLocalStorage("members")

    This is converted to a list with Json2List and then each record there (currently a string per member) is converted to a new list that has maps. Then the table.load method is used to load the available members.

    3. As each member has edit/delete, a method to trap the events is created..

    Code:
    'table click events
    Sub tblmembers_click(e As BANanoEvent)
        e.StopPropagation
        
    Dim prefix As String = tbl.GetPrefix(e.ID)
        
    Dim rkey As String = tbl.GetKey(e.ID)
        
    Select Case prefix
            
    Case "edit"
                action = 
    "update"
                modMember1.ClearMember1(
    Page)
                modMember2.ClearMember2(
    Page)
                modMember3.ClearMember3(
    Page)
                
    Dim strMember As String = ReadMember(rkey)
                
    Page.SetLocalStorage("member", strMember)
                modMember1.ReadMember1(
    Page,strMember)
                modMember2.ReadMember2(
    Page,strMember)
                modMember3.ReadMember3(
    Page,strMember)
                
    'refresh controls
                Page.refresh
                member.Open
            
    Case "delete"
                
    'ask the user
                'save this memberid for later retrieval
                Page.SetLocalStorage("memberid", rkey)
                
    'call a method existing in this application
                '_banano_atm_pgindex.deletemember();
                Dim onYes As String = App.CallBack("pgindex","deletemember",Array As String(""))
                
    Page.SweetModalConfirm($"Confirm Delete: ${rkey}"$, _
                
    "Are you sure that you want to delete this record? You will not be able to undo your changes.", onYes, "")
        
    End Select
    End Sub
    This table methodlogy based on how the FlowChart app works was applied for this project as a once off for a consistent code outlook.

    Ta!
     
    Johan Hormaza and joulongleu like this.
Loading...
  1. This site uses cookies to help personalise content, tailor your experience and to keep you logged in if you register.
    By continuing to use this site, you are consenting to our use of cookies.
    Dismiss Notice