So now that we know the basics of B4JS, lets make something real using a lot of what we have learned together with some ABMaterial components.
In this tutorial, we are going to create a simple calculator where all our ‘logic’ happens on the browsers side.
A video of what we are going to make:
Great, so lets get started!
First we are going to create the B4JS part: the logic of the calculator. This is pretty simple and we make use of the JavaScript eval() function to do the actual calculation.
'Class module Sub Class_Globals ' use public or dim if you want to share this variable over ALL B4JS classes ' use private if only within this class Public CurrentInput As String ' to access the constants Public ABM As ABMaterial 'ignore ' so we can use an msgbox Public Page As ABMPage 'ignore, just to be able to run ABMPage functions End Sub 'Initializes the object. You can NOT add parameters to this method. 'MUST be called InitializeB4JS is automatically called when using this class Public Sub InitializeB4JS End Sub public Sub ButtonPressed(key As String) As Boolean Select Case key Case "=" If CurrentInput "" Then CurrentInput = Page.B4JSRunInlineJavascriptMethod("evaluate", Array As String(CurrentInput)) End If Case "Del" If CurrentInput.Length > 0 Then CurrentInput = CurrentInput.SubString2(0, CurrentInput.Length - 1) End If Case Else CurrentInput = CurrentInput & key End Select Dim ResultLabel As ABMLabel 'ignore ' use the same key as when you created it ResultLabel.B4JSUniqueKey = "ResultLabel" ' we must use the B4JSText, not the normal Text property in a B4JS class ResultLabel.B4JSText = CurrentInput ' consume the event, if any server one should exist Return True End Sub public Sub OnMouseEnter(uniqueID As String) As Boolean Page.B4JSRunInlineJavascriptMethod("setCSS", Array As String(uniqueID, "background-color: #cacaca !important")) ' consume the event, if any server one should exist Return True End Sub public Sub OnMouseLeave(uniqueID As String) As Boolean Page.B4JSRunInlineJavascriptMethod("setCSS", Array As String(uniqueID, "background-color: #f5f5f5 !important")) ' consume the event, if any server one should exist Return True End Sub #if JAVASCRIPT function evaluate(s) { // so we get back a string return '' + eval(s); } function setCSS(id, val) { // we got the button, but we want the cell (which is its parent parent) $('#' + id).parent().parent().attr('style', val); } #End If
Notes:
As said in a previous tutorial, when we use a B4JS class in a ABM components B4JSOn… method, it gets its own instance. This is not very practical for our calculator as the CurrentInput variable must be shared. If we don’t make CurrentInput public, then each button will have its own CurrentInput.
As we don’t want any communication with the server, each method we are going to call returns true: consuming the event on the browser side.
When we dim the ResultLabel label, we do not initialize it again. To remove the warning in B4J, you can just add the ‘ignore after the dim. But what we MUST do, is set the B4JSUniqueKey. It must be the same as what we will set in the next part, the normal ABM Web Page.
Now we are ready to build the graphical UI part in ABMaterial and use the B4JS methods we created here.
We make some themes for our buttons and input field:
public Sub BuildTheme() ' start with the base theme defined in ABMShared theme.Initialize("pagetheme") theme.AddABMTheme(ABMShared.MyTheme) theme.AddCellTheme("border") theme.Cell("border").BorderColor = ABM.COLOR_BLACK theme.Cell("border").BorderWidth = 1 theme.Cell("border").BorderStyle = ABM.BORDER_SOLID theme.Cell("border").VerticalAlign = True theme.Cell("border").Align = ABM.CELL_ALIGN_RIGHT theme.AddRowTheme("white") theme.Row("white").BackColor = ABM.COLOR_GREY theme.Row("white").BackColorIntensity = ABM.INTENSITY_LIGHTEN4 theme.AddLabelTheme("right") theme.Label("right").Align = ABM.TEXTALIGN_RIGHT theme.AddLabelTheme("white") theme.Label("white").ForeColor = ABM.COLOR_WHITE theme.Label("white").Align = ABM.TEXTALIGN_CENTER theme.AddLabelTheme("black") theme.Label("black").ForeColor = ABM.COLOR_BLACK theme.Label("black").Align = ABM.TEXTALIGN_CENTER theme.AddCellTheme("white") theme.Cell("white").BackColor = ABM.COLOR_GREY theme.Cell("white").BackColorIntensity = ABM.INTENSITY_LIGHTEN4 theme.Cell("white").VerticalAlign = True theme.Cell("white").Align = ABM.CELL_ALIGN_CENTER theme.Cell("white").Clickable = True theme.AddCellTheme("black") theme.Cell("black").BackColor = ABM.COLOR_BLACK theme.Cell("black").VerticalAlign = True theme.Cell("black").Align = ABM.CELL_ALIGN_CENTER theme.Cell("black").Clickable = True theme.AddCellTheme("green") theme.Cell("green").BackColor = ABM.COLOR_GREEN theme.Cell("green").VerticalAlign = True theme.Cell("green").Align = ABM.CELL_ALIGN_CENTER theme.Cell("green").Clickable = True End Sub
In BuildPage() we create our grid layout:
public Sub BuildPage() ' initialize the theme BuildTheme ' initialize this page using our theme page.InitializeWithTheme(Name, "/ws/" & ABMShared.AppName & "/" & Name, False, ABMShared.SessionMaxInactiveIntervalSeconds, theme) page.ShowLoader=True page.PageHTMLName = "index.html" page.PageTitle = "Template" page.PageDescription = "Template" page.PageKeywords = "" page.PageSiteMapPriority = "" page.PageSiteMapFrequency = ABM.SITEMAP_FREQ_YEARLY page.ShowConnectedIndicator = True ' create the page grid page.AddRows(1,True,"").AddCells12(1,"") page.AddRowsM(1,True, 0,0,"").AddCellsOSMP(1,0,0,0,12,12,12,0,0,0,0,"border") page.AddRowsM(4,True, 0,0,"white").AddCellsOSMP(4,0,0,0,3,3,3,0,0,0,0,"") page.AddRowsM(1,True, 0,0,"").AddCellsOSMP(1,0,0,0,12,12,12,0,0,0,0,"") page.AddRows(5,True,"").AddCells12(1,"") page.BuildGrid 'IMPORTANT once you loaded the complete grid AND before you start adding components End Sub
And in ConnectPage() we build the calculator:
public Sub ConnectPage() Dim ResultLabel As ABMLabel ResultLabel.Initialize(page, "ResultLabel", "", ABM.SIZE_H4, True, "right") ' we are going to use this component on the B4JS side, so give it a UNIQUE key ResultLabel.B4JSUniqueKey = "ResultLabel" ResultLabel.PaddingRight = "10px" page.Cell(2,1).AddComponent(ResultLabel) ' setting a fixed height to the cell page.Cell(2, 1).SetFixedHeight(90, False) ' a list with all the buttons so we can easily iterate through them to build the buttons Dim Buttons As List = Array As String("7", "8", "9", "/", "4", "5", "6", "*", "1", "2", "3", "-", ".", "0", "Del", "+", "=") Dim ButtonPos As Int = 0 For x = 1 To 4 For y = 1 To 4 ' we use just a label for the 'button' Dim btn As ABMLabel ' as we will raise events from the cell this time, we must give it also a UNIQUE key page.Cell(2+x, y).B4JSUniqueKey = "btn" & ButtonPos If y < 4 Then ' the first three button (white) of the row btn.Initialize(page, "" & ButtonPos, Buttons.Get(ButtonPos), ABM.SIZE_H4, True, "black") btn.IsTextSelectable = False ' setting the white theme page.Cell(2+x, y).UseTheme("white") ' attaching our B4JS methods to the Cell events. We pass the labels ID that we can then use in the Javascript SetCSS method page.Cell(2+x, y).B4JSOnMouseEnter("B4JSCalculator", "OnMouseEnter", Array As String("btn" & ButtonPos)) page.Cell(2+x, y).B4JSOnMouseLeave("B4JSCalculator", "OnMouseLeave", Array As String("btn" & ButtonPos)) Else ' the last button in the row (black). We don't set a hover effect on theù btn.Initialize(page, "" & ButtonPos, Buttons.Get(ButtonPos), ABM.SIZE_H4, True, "white") btn.IsTextSelectable = False page.Cell(2+x, y).UseTheme("black") End If ' all the cells have a click event and we pass the labels text to the B4JS function to use in the Select Case page.Cell(2+x, y).B4JSOnClick("B4JSCalculator", "ButtonPressed", Array As String(Buttons.Get(ButtonPos))) ' also setting a fixed height page.Cell(2+x, y).SetFixedHeight(90, False) ' add we add the component as an Array component page.Cell(2+x, y).AddArrayComponent(btn, "btn") ' next button ButtonPos = ButtonPos + 1 Next Next ' finally we also add our last button, the = Dim btn As ABMLabel ' setting the UNIQUE key page.Cell(7, 1).B4JSUniqueKey = "btn" & ButtonPos btn.Initialize(page, "" & ButtonPos, Buttons.Get(ButtonPos), ABM.SIZE_H4, True, "white") btn.IsTextSelectable = False ' using the green theme page.Cell(7, 1).UseTheme("green") ' also using the ButtonPressed function from B4JS page.Cell(7, 1).B4JSOnClick("B4JSCalculator", "ButtonPressed", Array As String(Buttons.Get(ButtonPos))) ' setting the height page.Cell(7, 1).SetFixedHeight(90, False) ' and finally adding it as an Array component page.Cell(7, 1).AddArrayComponent(btn, "btn") ' and just to be sure if our server is still synced with the browser when we need it, lets show an msgbox Dim btnServerResult As ABMButton btnServerResult.InitializeFlat(page, "btnServerResult", "", "", "Hey, server what is my current result?", "") page.Cell(9, 1).AddComponent(btnServerResult) ' refresh the page page.Refresh ' Tell the browser we finished loading page.FinishedLoading ' restoring the navigation bar position page.RestoreNavigationBarPosition End Sub
Finally, we add the msgbox to the button on the server so we can prove our server can still receive the current browser situation of the ABMInput field when needed.
Sub btnServerResult_Clicked(Target As String)
Dim ResultLabel As ABMLabel = page.Component("ResultLabel")
page.Msgbox("msgbox", "Your complex calculation is now " & ResultLabel.Text, "Result", "OK", False, ABM.MSGBOX_POS_CENTER_CENTER, "")
End Sub
I've added a lot of comments in the source code so it will be easier to follow.
I've thought a long time on how we could connect the ABM UI in a clean way with the B4JS classes and I'm satisfied with the result. Some may argue why the introduction of B4JSUniqueKey and not just using the ID but it was a real necessity. ABM is actually very smart in how IDs work. It keeps track of its parents and makes it unique if needed. However, as B4JS is ‘compiled’ it doesn’t has this information when running.
For example it is quite possible that at compile time some components don’t even exist yet. (actually, most of them don’t as they are created in ConnectPage()). So the link between the B4JS component and its future ABM counterpart must be done by you, the programmer.
Another thing you could ask is why having .Text and .B4JSText, why can’t the same be used. In theorie there wouldn’t be a problem with that, except an ABMComponent has a lot more properties and methods than what B4JS can do. To distinguish which properties are available in B4JS, I gave them a prefix.
Not all ABM Components properties and methods will be converted. Gradually, some may be added but it is never the intention to convert them all to B4JS. Frankly, it would be an impossible task. ABM is so huge (took me over 2 years day and night to get where we are now). Other components will also be ‘converted’ in the future too, but they will be done on a ‘on-need’ base.
In conclusion, as Mindful mentioned: this paves the way to progressive web apps!
I’ll try to make a download of ABM 4.25 As soon as possible so the donators can have a go with B4JS very soon.
For the ones interested in the relevant JavaScript source code of our B4JS class:
var _currentinput=""; var _abm; var _page; function b4js_b4jscalculator() { var self; this.initializeb4js=function(){ self=this; try { } catch(err) { console.log(err.message + ' ' + err.stack); } }; this.buttonpressed=function(_key){ try { switch ("" + _key) { case "" + "=": if (_currentinput!="") { _currentinput = evaluate(_currentinput); } break; case "" + "Del": if (_currentinput.length>0) { _currentinput = _currentinput.substring(0,_currentinput.length-1); } break; default: _currentinput = _currentinput+_key; break; } var _resultlabel={}; _resultlabel.b4jsvar=$('[data-b4js="resultlabel"]'); _resultlabel.b4jsvar.html(b4js_buildtext(_currentinput, false)); return true; } catch(err) { console.log(err.message + ' ' + err.stack); } }; this.onmouseenter=function(_uniqueid){ try { setCSS(_uniqueid, "background-color: #cacaca !important"); return true; } catch(err) { console.log(err.message + ' ' + err.stack); } }; this.onmouseleave=function(_uniqueid){ try { setCSS(_uniqueid, "background-color: #f5f5f5 !important"); return true; } catch(err) { console.log(err.message + ' ' + err.stack); } }; }; function evaluate(s) { // so we get back a string return '' + eval(s); } function setCSS(id, val) { // we got the button, but we want the cell (which is its parent parent) $('#' + id).parent().parent().attr('style', val); }
Alwaysbusy