B4J Tutorial [ABMaterial] 1.09 all about flexibility

Discussion in 'B4J Tutorials' started by alwaysbusy, Apr 14, 2016.

  1. alwaysbusy

    alwaysbusy Expert Licensed User

    This is the first of a two part tutorial on some big changes I'm making to ABMaterial in 1.09. It should be fully backwards compatible so no (or very, very little) changes will be needed from your side on your existing projects if you stay working the pre-1.09 way.

    I've rewritten the demo app in a dynamic way using the 'static' version and it went swift. And it creates a lot of new potential!

    PART 1: Flexibility
    -----------------
    When ABMaterial 0.x first came out, it was especially useful for static pages. Since then, I gradually made more and more components dynamic. In 1.09, every component gets the upgrade (even ModalSheets!).

    In the next download you will find a completely 'rewritten' Demo app being 100% dynamic.

    So the steps I had to do to make a Page dynamic:
    For the demo app, first I did this in ABMShared for preparation (the steps look very intensive, but I've tried to make it as understandable as possible):

    1. Create a new method: ConnectNavigationBar()
    2. Move all lines where you build the topbar and sidebar to ConnectPage()

    NOTES:
    a. In BuildNavigationBar, we have to add a DUMMY item for the top and sidebar:
    Code:
    ' you must add at least ONE dummy item if you want to add items to the topbar   in ConnectNaviagationBar
       page.NavigationBar.AddTopItem("DUMMY""DUMMY"""""False)

       
    ' you must add at least ONE dummy item if you want to add items to the sidebar
       page.NavigationBar.AddSideBarItem("DUMMY""DUMMY""""")
    b. In ConnectNavigationBar, we have to remove those DUMMIES
    Code:
    ' Clear the dummies we created in BuildNavigationBar
        page.NavigationBar.Clear
    Full code:
    Code:
    Sub BuildNavigationBar(page As ABMPage, Title As String, logo As String, ActiveTopReturnName As String, ActiveSideReturnName As String, ActiveSideSubReturnName As String)
       
    page.SetFontStack("arial,sans-serif")

       
    ' we have to make an ABMImage from our logo url
        Dim sbtopimg As ABMImage
        sbtopimg.Initialize(
    page"sbtopimg", logo, 1)
        sbtopimg.SetFixedSize(
    23649)

       
    page.NavigationBar.Initialize(page"nav1", ABM.SIDEBAR_MANUAL_HIDEMEDIUMSMALL, Title, TrueTrue33048, sbtopimg, ABM.COLLAPSE_ACCORDION, "nav1theme"
       
    page.NavigationBar.TopBarDropDownConstrainWidth = False
       
    page.NavigationBar.ActiveTopReturnName = ActiveTopReturnName
       
    page.NavigationBar.ActiveSideReturnName = ActiveSideReturnName
       
    page.NavigationBar.ActiveSideSubReturnName = ActiveSideSubReturnName
     
       
    ' you must add at least ONE dummy item if you want to add items to the topbar   in ConnectNaviagationBar
       page.NavigationBar.AddTopItem("DUMMY""DUMMY"""""False)
     
       
    ' you must add at least ONE dummy item if you want to add items to the sidebar 
       page.NavigationBar.AddSideBarItem("DUMMY""DUMMY""""")
    End Sub

    Sub ConnectNavigationBar(page As ABMPage)
        
    ' Clear the dummies we created in BuildNavigationBar
        page.NavigationBar.Clear
     
        
    ' new behaviour: on each top item you can set if it should hide of not on a medium or small device.
        page.NavigationBar.AddTopItem("Contact""""mdi-action-account-circle"""False)

        
    ' shortened for this tutorial, you move them all!
        ' --------------------------------------------
        page.NavigationBar.AddSideBarItem("Icons""Icons""mdi-action-account-circle""../IconsPage/abmaterial-material-icons.html")  
        
    '... the rest
    End Sub
    3. Create a new Method: ConnectFooter() and ConnectFooterFixed()
    4. Move all lines where you add components to ConnectFooter() & ConnectFooterFixed()
    Code:
    Sub BuildFooter(page As ABMPage)     
        
    page.Footer.AddRows(1True"").AddCellsOS(2,0,0,0,6,6,6"")
        
    page.Footer.BuildGrid 'IMPORTANT once you loaded the complete grid AND before you start adding components 
     
        
    page.Footer.UseTheme("footertheme")     
    End Sub

    Sub ConnectFooter(page As ABMPage) 
        
    Dim lbl1 As ABMLabel
        lbl1.Initialize(
    page"footlbl1""Blog: Alwaysbusy's Corner{BR}{BR}B4J by Anywhere Software{BR}Materialize CSS by students from Carnegie Mellon University",ABM.SIZE_PARAGRAPH, False"whitefc")
        
    page.Footer.Cell(1,1).AddComponent(lbl1)
     
        
    Dim lbl2 As ABMLabel
        lbl2.Initialize(
    page"footlbl2""ABMaterial Copyright @2015-2016{BR}By Alain Bailleul{BR}{BR}Email: alain.bailleul@telenet.be",ABM.SIZE_PARAGRAPH, False"whitefc")
        
    page.Footer.Cell(1,2).AddComponent(lbl2) 
    End Sub

    Sub BuildFooterFixed(page As ABMPage) 
        
    page.isFixedFooter= True
        
    ' because we have a fixed footer at the bottom, we have to adjust the padding of the body in pixels
        page.PaddingBottom = 200
     
        
    page.Footer.AddRows(1True"").AddCellsOS(2,0,0,0,6,6,6"")
        
    page.Footer.BuildGrid 'IMPORTANT once you loaded the complete grid AND before you start adding components 
     
        
    page.Footer.UseTheme("footertheme"
    End Sub

    Sub ConnectFooterFixed(page As ABMPage)     
        
    Dim lbl1 As ABMLabel
        lbl1.Initialize(
    page"footlbl1""Blog: Alwaysbusy's Corner{BR}{BR}B4J by Anywhere Software{BR}Materialize CSS by students from Carnegie Mellon University",ABM.SIZE_PARAGRAPH, False"whitefc")
        
    page.Footer.Cell(1,1).AddComponent(lbl1)
     
        
    Dim lbl2 As ABMLabel
        lbl2.Initialize(
    page"footlbl2""ABMaterial Copyright @2015-2016{BR}By Alain Bailleul{BR}{BR}Email: alain.bailleul@telenet.be",ABM.SIZE_PARAGRAPH, False"whitefc")
        
    page.Footer.Cell(1,2).AddComponent(lbl2) 
    End Sub
    Ok preparations are done, let's move to an example of making a Page dynamic now:
    1. Create a new method: ConnectPage()
    2. Move all lines where you add components, modal sheets etc to ConnectPage()
    3. Add the following line to the BuildPage() method (I'll explain further why):
    Code:
    page.ShowLoaderType=ABM.LOADER_TYPE_MANUAL ' NEW
    4. Add the following line to the start of ConnectPage()
    Code:
    'NEW
        ABMShared.ConnectNavigationBar(page)
    5. Add the following lines to the end of ConnectPage()
    Code:
    ' also add the components to the footer
        ABMShared.ConnectFooter(page)
     
        
    page.Refresh ' IMPORTANT
     
        
    ' NEW, because we use ShowLoaderType=ABM.LOADER_TYPE_MANUAL
        page.FinishedLoading 'IMPORTANT
    Full code:
    Code:
    public Sub BuildPage()
        
    ' initialize the theme
        BuildTheme
     
        
    ' initialize this page using our theme
        page.InitializeWithTheme(Name, "/ws/" & AppName & "/" & Name, False, theme)
        
    page.ShowLoader=True
        
    page.ShowLoaderType=ABM.LOADER_TYPE_MANUAL ' NEW
        page.PageTitle = "ABMCodeLabel"
        
    page.PageDescription = "The code label component " 
        
    page.PageHTMLName = "abmaterial-code-label.html"
        
    page.PageKeywords = "ABMaterial, material design, B4X, B4J, SEO, framework, search engine optimization"
        
    page.PageSiteMapPriority = "0.50"
        
    page.PageSiteMapFrequency = ABM.SITEMAP_FREQ_MONTHLY
        
    page.UseGoogleAnalytics(ABMShared.TrackingID, Null' IMPORTANT Change this to your own TrackingID !!!!!!!
         
        ABMShared.BuildNavigationBar(
    page"ABMCodeLabel""../images/logo.png""""Controls""ABMCodeLabel")
         
        
    ' create the page grid
        page.AddRows(5,True"").AddCells12(1,"")
        
    page.BuildGrid 'IMPORTANT once you loaded the complete grid AND before you start adding components
         
        ABMShared.BuildFooter(
    page)
    End Sub

    Sub ConnectPage()
        
    'NEW
        ABMShared.ConnectNavigationBar(page)
        
    ' add paragraph 
        page.Cell(1,1).AddComponent(ABMShared.BuildParagraph(page,"par1","ABMCodeLabels are blocks that represent source programming code within your page.  It can be used, like for this app, to make a help website or tutorial that explains your library.") ) 
        
    ' add paragraph 
        page.Cell(1,1).AddComponent(ABMShared.BuildParagraph(page,"par2","There are two methods to setup your source code string: Using a B4J List object or using the B4J Smart Strings.  An example of both, you can use the one you feel most comfortable with.  Note, Smart Strings cannon show everything: Using as code Sub Name() ... End Sub ... Sub Name2() ... End Sub will not work with Smart Strings!") ) 
     
        
    ' add codeblock 
        Dim code1 As StringBuilder
        code1.Initialize
        code1.Append(
    "// add codeblock").Append(CRLF
        code1.Append(
    "Dim code2 As StringBuilder").Append(CRLF)
        code1.Append(
    "code2.Initialize").Append(CRLF)
        code1.Append(
    "code2.Append(""Dim btn4 As ABMButton"").Append(CRLF)").Append(CRLF)
        code1.Append(
    "code2.Append(""btn4.Initializefloating(page, ""btn4"", ""mdi-image-palette"", ""darkred"")"").Append(CRLF)").Append(CRLF)
        code1.Append(
    "code2.Append(""btn4.Large = True"").Append(CRLF)").Append(CRLF)
        code1.Append(
    "code2.Append(""page.Cell(5,1).AddComponent(btn4)"").Append(CRLF)").Append(CRLF)
        code1.Append(
    "page.Cell(6,1).AddComponent(ABMShared.BuildCodeBlock(page, ""code2"", code2))").Append(CRLF)
         
        
    page.Cell(2,1).AddComponent(ABMShared.BuildCodeBlock(page"code1", code1))
     
        
    ' add codeblock 
        Dim code4 As StringBuilder
        code4.Initialize
        code4.Append(
    "// add codeblock").Append(CRLF
        code4.Append(
    "Dim code3 As String = $""Dim btn4 As ABMButton").Append(CRLF)
        code4.Append(
    "btn4.Initializefloating(page, ""btn4"", ""mdi-image-palette"", ""darkred"")").Append(CRLF)
        code4.Append(
    "btn4.Large = True").Append(CRLF)
        code4.Append(
    "page.Cell(5,1).AddComponent(btn4)""$").Append(CRLF)
        code4.Append(
    "page.Cell(6,1).AddComponent(ABMShared.BuildCodeBlockFromSmartString(page, ""code3"", code3))").Append(CRLF)
             
        
    page.Cell(2,1).AddComponent(ABMShared.BuildCodeBlockFromSmartString(page"code4", code4))
     
        
    ' also add the components to the footer
        ABMShared.ConnectFooter(page)
     
        
    page.Refresh ' IMPORTANT
     
        
    ' NEW, because we use ShowLoaderType=ABM.LOADER_TYPE_MANUAL
        page.FinishedLoading 'IMPORTANT
    End Sub
    6. In Page_ready() call our ConnectPage() method:
    Code:
    Sub Page_Ready()
        
    Log("ready!")
     
        
    ' NEW
        ConnectPage
     
        
    page.RestoreNavigationBarPosition
    End Sub
    Done! We made our page dynamic.

    Some explanation what is happening:

    In a 'static' page, all the components are written in the .html file. In a 'dynamic' page they are not. Only when you connect as a user, the components are inserted in their browser.

    This seems trivial, but this actually means we can 'write' components (html, css and javascript) at run-time, depending on e.g. the user that logged in. Some user is allowed to see one chart, another user another chart. Or one may be able to delete a record from your database, another is not. Or showing a certain modal sheet depending on the user without having to add all possibilities in the HTML. Or, like will be explained in tutorial part 2, show the page in different languages depending on who logged in!

    Why did we need to add those extra lines in BuildPage() and ConnectPage()?
    page.ShowLoaderType=ABM.LOADER_TYPE_MANUAL means: We are going to take control when the 'running circles' should hide. As the page is loaded dynamical, without this line the user actually sees the page being build. This can be confusing (e.g. the user sees a button, wants to click it, but suddenly a chart pops up).

    So now we have to tell manualy when everything is in its place, using page.FinishedLoading in ConnectPage().

    Note that we also here run our FooterConnect() and a page.refresh()! This MUST be done, as it gives the action from the server to the browser to actually perform your changes.

    How come we don't have to add those page.Needs...() properties for the components we'll add outside BuildPage()?
    When you run (build) you app, ABMaterial wil inspect the B4J source code and try to find out what components you'll planning to use. It generates a text file next to your .jar called: copymewithjar.needs. As the name suggests, when you 'install' your app to your real server, you MUST copy this file next to your .jar file. Why? Because at that time, the B4J source code is no longer available for expection so it uses this file to fill in the page.Needs...() when you start the .jar.

    This is an example of a generated copymewithjar.needs file for one of my apps:
    Code:
    ABMApplication:NeedsInput;NeedsTextArea;NeedsMask
    AgendaPage:NeedsCalendar;NeedsSwitch
    ProspectPage:NeedsRadio;NeedsInput;NeedsTextArea;NeedsMask;NeedsCombo;NeedsSlider;NeedsGoogleMap;NeedsLists;NeedsDateTimeScroller;NeedsSwitch
    ReferentiesPage:NeedsRadio;NeedsInput;NeedsTextArea;NeedsMask;NeedsCombo;NeedsSlider;NeedsGoogleMap;NeedsLists
    StartPage:NeedsInput;NeedsTextArea;NeedsMask;NeedsPagination;NeedsTable;NeedsSortingTable;NeedsTabs;NeedsCombo;NeedsDateTimeScroller;NeedsSwitch
    I've tried to do this analysis to a certain extend. (e.g. it can 'see' if you are needing a component you declared in a module and are using in your page class). But, if somehow it was unable to find it because your code structure is very complex and jumps all around, you will still need to set the page.Needs...() property yourself in BuildPage(). But this shouldn't happen very often. e.g. while converting all my apps, I never had to do it once.

    Still a lot of testing to do, but it's looking good ;)

    This concludes Part 1. In Part 2 I'll show you how we can use localization in ABMaterial.

    Alain
     
    Last edited: Apr 14, 2016
    Mashiane, vfafou, imbault and 7 others like this.
  2. Eric Baker

    Eric Baker Member Licensed User

    Wow, I am very excited to read this. Still just getting started (part time) with the framework and it looks extremely promising. I haven't given it much thought that the pages up to now were static. This may or may not have caused some headache in the future and these changes drive me even more to continue to explore and support ABMaterial.
     
    alwaysbusy likes this.
  3. alwaysbusy

    alwaysbusy Expert Licensed User

    It wasn't 100% static any more since a couple of versions, but now it is completely dynamic. It was especially the localization topic (tutorial will be in Part 2) that raised a need for it.
     
  4. amminf

    amminf Active Member Licensed User

    Alain,

    Impressive !

    Congratulations
     
    alwaysbusy likes this.
  5. Roberto P.

    Roberto P. Well-Known Member Licensed User

    Excellent improves!
    It would be interesting also enter Ajax to optimize the loading and updating of a part of the page.
    I hope not too much to ask.
    Thank you
     
  6. alwaysbusy

    alwaysbusy Expert Licensed User

    ABMaterial uses some ajax, but only where applicable. (e.g. to upload a file). ABMaterial is using websockets, which is the modern way browsers handle communication. Ajax is the old way of doing things. ABMaterial is already fully optimized to only update parts of the page. This is why every component has its own refresh. If you use a components refresh, nothing else in the page is touched. Of course if you always use page.refresh then the whole page is refreshed. It's up to you as a programmer to minimize the number of component refreshes needed.
     
    amminf and Erel like this.
  7. imbault

    imbault Well-Known Member Licensed User

    Very great, Alain, when should we expect that V1.09?
    Thanks for all your work
     
  8. alwaysbusy

    alwaysbusy Expert Licensed User

    1.08 is just out, so in a week or three I guess. Still lots to program, test etc...
     
  9. Harris

    Harris Well-Known Member Licensed User

    I don't know what to think... yet.
    I shall have to see how it affects what I have already built.

    Typically, what you do is all good for us. I just have to apply, and understand it.
    I am trying to develop a webapp for a client where new ABM updates don't affect my current implementation.

    Thanks
     
  10. alwaysbusy

    alwaysbusy Expert Licensed User

    @Harris. It does not affect you existing project. And as you haven't encountered a situation yet where you needed that functionality, you probably won't. I just wanted to show that from 1.09 on, you'll have an additional way of making your app, not a replacement.
     
    Harris and amminf like this.
  11. Harris

    Harris Well-Known Member Licensed User

    Sub Page_NavigationbarClicked(Action As String, Value As String)
    page.ws.Session.SetAttribute("UserAction", Action)

    Sub AddCaseBtn_Clicked(Target As String)
    If page.ws.Session.GetAttribute2( "UserAction", "") = "Company" Then

    With 1.10, do I still need to use above to prevent each user from stomping what other users have selected (or are viewing)?

    Thanks
     
  12. alwaysbusy

    alwaysbusy Expert Licensed User

    Yes, the same principles still apply
     
    Harris likes this.
  13. Harris

    Harris Well-Known Member Licensed User

    In 1.10

    I have noticed that the page.Refresh may want to be inserted in the Page.Ready(), rather than ConnectPage(), since other methods may be called after this (your simple example).

    Code:
    Sub Page_Ready()
        
    Log("Employee ready!")
        Page_name = 
    page.ws.Session.GetAttribute2( "UserAction""")  ' needed if connectpage() calls methods that must know Case and IF statements directives
       
        ConnectPage
        
    page.RestoreNavigationBarPosition
        
    page.NavigationBar.Title = page.ws.Session.GetAttribute2( "UserAction""")  ' set the current page title ( page.PageTitle - doesn't work here...)
      

    ' called after ConnectPage()
        LoadCases(1)  ' without page refresh below, this grid won't be shown
       
        
    page.Refresh
           
        
    page.Footer.Cell(2,1).Refresh
       
        
    ' if visibility was to hide( by default ) , then unhide them
       
        
    If (Page_name = "Vehicles")  Then
           
    page.Row(1).Refresh   
        
    End If
        
    If (Page_name = "Zones")  Then
           
    page.Row(2).Refresh   
        
    End If
        
    If (Page_name = "Employees")  Then
           
    page.Row(3).Refresh   
        
    End If
       
    End Sub
    Updating one's many pages to 1.10 dynamic is rather trivial... when you figure out what goes where.
    With my "One Page, Many Uses" strategy, and setting a page.session "UserAction" attribute, all functions well.

    Good on ya!
     
    alwaysbusy likes 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