See for a B4JS introduction: https://www.b4x.com/android/forum/threads/abmaterial-b4js-0-9.90249/
-------------------------------------------------------------------------------
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 of 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.
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
In BuildPage() we create our grid layout
And in ConnectPage() we build the calculator
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.
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.
It is impossible to convert all ABM Components properties and methods. 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.
I'll try to make a download of ABM 4.25 by the end of next week 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:
Alwaysbusy
-------------------------------------------------------------------------------
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 of 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.
B4X:
'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
B4X:
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
B4X:
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
B4X:
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.
B4X:
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.
It is impossible to convert all ABM Components properties and methods. 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.
I'll try to make a download of ABM 4.25 by the end of next week 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:
B4X:
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
Last edited: