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:
b. In ConnectNavigationBar, we have to remove those DUMMIES
Full code:
3. Create a new Method: ConnectFooter() and ConnectFooterFixed()
4. Move all lines where you add components to ConnectFooter() & ConnectFooterFixed()
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):
4. Add the following line to the start of ConnectPage()
5. Add the following lines to the end of ConnectPage()
Full code:
6. In Page_ready() call our ConnectPage() method:
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:
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
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:
B4X:
' 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", "", "")
B4X:
' Clear the dummies we created in BuildNavigationBar
page.NavigationBar.Clear
Full code:
B4X:
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(236, 49)
page.NavigationBar.Initialize(page, "nav1", ABM.SIDEBAR_MANUAL_HIDEMEDIUMSMALL, Title, True, True, 330, 48, 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
4. Move all lines where you add components to ConnectFooter() & ConnectFooterFixed()
B4X:
Sub BuildFooter(page As ABMPage)
page.Footer.AddRows(1, True, "").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(1, True, "").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):
B4X:
page.ShowLoaderType=ABM.LOADER_TYPE_MANUAL ' NEW
B4X:
'NEW
ABMShared.ConnectNavigationBar(page)
B4X:
' 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:
B4X:
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
B4X:
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:
B4X:
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: