B4J Tutorial [ABMaterial] B4JS - 06 The UI Connection

Discussion in 'B4J Tutorials' started by alwaysbusy, Mar 8, 2018.

  1. alwaysbusy

    alwaysbusy Expert Licensed User

    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.
    Code:
    '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 StringAs 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 StringAs 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 StringAs 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
    Code:
    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
    Code:
    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,True0,0,"").AddCellsOSMP(1,0,0,0,12,12,12,0,0,0,0,"border")
       
    page.AddRowsM(4,True0,0,"white").AddCellsOSMP(4,0,0,0,3,3,3,0,0,0,0,"")
       
    page.AddRowsM(1,True0,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
    Code:
    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(21).SetFixedHeight(90False)
     
       
    ' 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(90False)
               
    ' 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(71).B4JSUniqueKey = "btn" & ButtonPos
     
       btn.Initialize(
    page"" & ButtonPos, Buttons.Get(ButtonPos), ABM.SIZE_H4, True"white")
       btn.IsTextSelectable = 
    False
       
    ' using the green theme
       page.Cell(71).UseTheme("green")
     
       
    ' also using the ButtonPressed function from B4JS
       page.Cell(71).B4JSOnClick("B4JSCalculator""ButtonPressed"Array As String(Buttons.Get(ButtonPos)))
       
    ' setting the height
       page.Cell(71).SetFixedHeight(90False)
       
    ' and finally adding it as an Array component
       page.Cell(71).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(91).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.
    Code:
    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:
    Code:
    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: Mar 8, 2018
  2. mindful

    mindful Active Member Licensed User

    In conclusion this paves they way to progressive web apps ;)

    Great work! Congrats
     
    joulongleu likes this.
  3. alwaysbusy

    alwaysbusy Expert Licensed User

    Up to a certain point, yes ;)
     
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