B4X: ABMaterial and using a CDN

cdn

Using a CDN (Content Delivery Network) for your WebApp can be very beneficial:

The Benefits of using a CDN:

  • Different domains. Browsers limit the number of concurrent connections (file downloads) to a single domain. …
  • Files may be pre-cached. …
  • High-capacity infrastructures. … (1200 world-wide)
  • Distributed data centers. …
  • Built-in version control. …
  • Usage analytics. …
  • Boosts performance and saves money.

ABM can already use a CDN for its own framework using the ABM.ActivateUseCDN(“DONATOR_KEY”, “https://cdn.jsdelivr.net/gh/RealAlwaysbusy/ABMaterial@v4.35/“), but from version 4.35 on, ABM will support Cloudinary as a build-in CDN system for your own WebApps.

Cloudinary has a great free plan which can cover a great deal of the webapps you make without any additional cost:

  • Total images and videos: 300,000
  • Managed storage: 10 GB
  • Monthly net viewing bandwidth: 20 GB

In ABM, it can be used for two things:

1. Automatic upload/update of the generated CSS/JS files (the ones you find next to each index.html).
2. Your own assets, like images, videos or outer ‘RAW’ files (xls, doc, …)

Creating a free account on Cloudinary is pretty straight forward. You make a login and give your ‘cloud’ a name. You will then be able to get your API_KEY and API_SECRET from the management console: https://cloudinary.com/console:

cloudinary1

1. Automatic upload/update when starting the .jar file.

Activate it in the main class:

' just some clear consts to explain the parameters
Dim UPLOAD_GENERATED_JSCSS As Boolean = True
Dim DO_NOT_UPLOAD_GENERATED_JSCSS As Boolean = False
Dim SHOW_DEBUG As Boolean = True
Dim DO_NOT_SHOW_DEBUG As Boolean = False
Dim CLEAR As Boolean = True
Dim DO_NOT_CLEAR As Boolean = False
  
#If RELEASE      
       ABM.ActivateCloudinary("DONATOR_KEY","CLOUD_NAME", "API_KEY", "API_SECRET",UPLOAD_GENERATED_JSCSS, "APPNAME", DO_NOT_SHOW_DEBUG)
#else
       ABM.ActivateCloudinary("DONATOR_KEY","CLOUD_NAME", "API_KEY", "API_SECRET",DO_NOT_UPLOAD_GENERATED_JSCSS, "APPNAME", SHOW_DEBUG)
#end if
' this is for 2, your own assets
ABM.CloudinaryLoadImages(CLEAR)
ABM.CloudinaryLoadAudioVideos(DO_NOT_CLEAR)
ABM.CloudinaryLoadRawFiles(DO_NOT_CLEAR)

What will this code do?
It will create a folder structure in you Cloudinary cloud:

APPNAME/css
APPNAME/js

Every time you start your jar, both these folders will be emptied (so do not put anything else in it!) and the most recent generated .js/.css files will be uploaded. Note: depending on the size of your app, this can take quite some time. e.g. for the demo app it takes about 3 minutes.

There is nothing more you have to do, ABM will have changed all the calls in the HTML from your local drive to the CDN.

2. Your own Assests:

As you can see in (1), we pre-load all the urls of the images/videos and other files. That way it is pretty simple to use the e.g. if we want to use an image using the ABM.CloudinaryGet() command:

Dim img4 As ABMImage
img4.Initialize(page, "img4", ABM.CloudinaryGet("demo/images/sample-1"), 1)
page.Cell(6,1).AddComponent(img4)

As parameter it takes the ‘publicId’ of the asset. In my case my APPNAME was demo, I created a folder images in it in the Cloudinary console and uploaded a file called sample-1.jpg so the public id becomes:

demo/images/sample-1

Note: I’ve noticed when uploading images via the cloudinary console, the public id cuts of the file extension.

The result of the ABM.CloudinaryGet command will be something like:

url: https://res.cloudinary.com/abmaterial/image/upload/v1536580044/demo/images/sample-1.jpg

It is very easy to upload and organize you assets through the Cloudinary console app:

cloudinary2

but sometimes you will want to upload it through B4X code.

So the following API methods do exist:

' using these 3 methods will automatically add them the the internal Cloudinary map
' so you can use them with the ABM.CloudinaryGet() method without reloading.
' The next time you restart your .jar, they will also be loaded.
ABM.CloudinaryUploadImage("publicId", fullFilePath)
ABM.CloudinaryUploadVideo("publicId", fullFilePath)
ABM.CloudinaryUploadRawFile("publicId", fullFilePath)

ABM.CloudinaryDeleteImage("publicId")
ABM.CloudinaryDeleteVideo("publicId")
ABM.CloudinaryDeleteRawFile("publicId")

ABM.CloudinaryDeleteAllImagesWithPrefix("prefix")
ABM.CloudinaryDeleteAllVideosWithPrefix("prefix")
ABM.CloudinaryDeleteAllRawFilesWithPrefix("prefix")

For the last 3 methods, you use a ‘prefix’ to delete files in bulk:

e.g. if I want to delete all images from my images folder, I would do:

ABM.CloudinaryDeleteAllImagesWithPrefix("demo/images/")

Cloudinary has a wide range of API methods (e.g. for image manipulation etc), and maybe I will expand the B4X API to support it too, but for the moment using it as a CDN only, it suffices.

Note that this is a Donator only feature. ABM 4.35 will be available in a couple of weeks.

This concludes the tutorial.

Alwaysbusy

Click here to Donation and support ABMaterial

B4X: ABMaterials B4JS – 07 The UI Connection (part 2)

b4jstut07

This is the second part of the UI (ABMaterial) Connection. I wanted to do this one before releasing ABM 4.25 because it contains some important concepts on defining the events for B4JS in an ABMPage.

I’ve created a B4JS Demo that includes the source code for all the tutorials (so far). It will be included in the download zip.

One of the major advantages of B4JS is that you can check a lot of the users input before heading to the server. And because we have some events (like KeyDown, KeyUp) that we do not have on the server side, we can even do some ‘masking’.

This example makes such a mask for a credit card (every 4 chars a space is inserted and a check is done). It is not finished (e.g. it does not take into account if the user puts its cursor in the middle), but it shows some major concepts of B4JS.

B4JSTut07image

Lets dive into the B4X code:

The B4JS class B4JSInputChecker:

<br>
'Class module
Sub Class_Globals
   Private Page As ABMPage 'ignore, just to be able to run ABMPage functions
   Private ToastID As Int
   Private ABM As ABMaterial 'ignore, just to be able to use ABM constants
End Sub

'Initializes the object. You can add parameters to this method if needed.
Public Sub InitializeB4JS

End Sub

public Sub InputDown(Key As String) As Boolean
   'Log("Down key: " &amp; Key)
   Dim cardInp As ABMInput 'ignore that it is not initialized, you can't initialize one in B4JS anyway
   cardInp.B4JSUniqueKey = "cardInp001"
   Dim CardNumber As String = cardInp.B4JSText
   If Key = "Backspace" Then
       If CardNumber.SubString2(0,CardNumber.Length-1).EndsWith(" ") Then
           CardNumber = CardNumber.SubString2(0,CardNumber.Length - 2)
           cardInp.B4JSText = CardNumber
           CheckCard(CardNumber.Replace(" ", ""))
           ' consume the event
           Return True
       End If
       ' let the Keydown do its thing
       Return False
   End If

   If CardNumber.Length = 19 Then
       CheckCard(CardNumber.Replace(" ", ""))
       Return True
   End If

   If Not(IsNumber(Key)) Then
       ToastID = ToastID + 1
       Page.B4JSShowToast("InputToast" &amp; ToastID, "red", "Please only enter numbers!", 3000, False)
       ' consume the event
       Return True
   End If

   ' let the Keydown do its thing
   Return False
End Sub

public Sub InputUp(KeyCode As Int) As Boolean
   'Log("Up keycode: " &amp; KeyCode)

   Dim cardInp As ABMInput 'ignore that it is not initialized, you can't initialize one in B4JS anyway
   cardInp.B4JSUniqueKey = "cardInp001"
   Dim CardNumber As String = cardInp.B4JSText

   ' we down't want to raise it for the combination keys
   ' AltGraph, Shift, Ctrl, Alt
   Select Case KeyCode
       Case 16, 17, 18
           ' consume the event
           Return True
       Case 8 ' and for the backspace, jump out too: we do not want to re-append the space
           ' consume the event
           CheckCard(CardNumber.Replace(" ", ""))
           Return True
   End Select

   Select Case CardNumber.Length
       Case 4,9,14
           cardInp.B4JSText = CardNumber &amp; " "
   End Select

   'a valid 4012 8888 8888 1881
   CheckCard(CardNumber.Replace(" ", ""))

   ' consume the event
   Return True
End Sub

public Sub CheckCard(CardNumber As String)
   'a valid 4012 8888 8888 1881
   Dim isValid As Boolean = Page.B4JSRunInlineJavascriptMethod("validateCCNum", Array As Object(CardNumber.Replace(" ", "")))
   Dim btnCheck As ABMButton 'ignore
   btnCheck.B4JSUniqueKey = "btnCheck"
   ' for our setButtonCSS method we do need the real ID of the button.  We can get this with the Page.B4JSGetComponentIDFromUniqueID() method!
   Dim ID As String = Page.B4JSGetComponentIDFromUniqueID("btnCheck")
   If isValid Then
       btnCheck.B4JSEnabled = True
       Page.B4JSRunInlineJavascriptMethod("setButtonCSS", Array As String(ID, "background-color: #4caf50 !important"))
   Else
       btnCheck.B4JSEnabled = False
       Page.B4JSRunInlineJavascriptMethod("setButtonCSS", Array As String(ID, "background-color: #F44336 !important"))
   End If
End Sub

' every method you want to call with a B4JSOn... call MUST return a boolean
public Sub InputGotFocus() As Boolean
   ToastID = ToastID + 1
   Page.B4JSShowToast("InputToast" &amp; ToastID, "red", "You are entering the Credit Card", 3000, False)
   ' consume the event
   Return True
End Sub

' every method you want to call with a B4JSOn... call MUST return a boolean
public Sub InputLostFocus() As Boolean
   ToastID = ToastID + 1
   Page.B4JSShowToast("InputToast" &amp; ToastID, "red", "Leaving the Credit Card field...", 3000, False)
   ' consume the event
   Return True
End Sub

' every method you want to call with a B4JSOn... call MUST return a boolean
public Sub CheckInput() As Boolean
   Dim switch1 As ABMSwitch  'ignore that it is not initialized, you can't initialize one in B4JS anyway
   switch1.B4JSUniqueKey = "switch001"

   Dim HasError As Boolean = False

   ToastID = ToastID + 1
   If Not(switch1.B4JSState) Then
       Page.B4JSShowToast("SwitchToast" &amp; ToastID, "green", "Please put the Switch to ON!", 3000, False)
       HasError = True
   End If

   ToastID = ToastID + 1
   Dim CheckBox1 As ABMCheckbox 'ignore that it is not initialized, you can't initialize one in B4JS anyway
   CheckBox1.B4JSUniqueKey = "CheckBox001"
   If Not(CheckBox1.B4JSState) Then
       Page.B4JSShowToast("CheckboxToast" &amp; ToastID, "green", "Please check the Checkbox!", 3000, False)
       HasError = True
   End If

   If HasError = False Then
       ToastID = ToastID + 1
       Page.B4JSShowToast("CheckboxInput" &amp; ToastID, "green", "All looks OK, well done!", 3000, False)
   End If
   ' consume the event
   Return True
End Sub

' every method you want to call with a B4JSOn... call MUST return a boolean
public Sub SwitchClick() As Boolean
   Dim switch1 As ABMSwitch  'ignore that it is not initialized, you can't initialize one in B4JS anyway
   switch1.B4JSUniqueKey = "switch001"

   ToastID = ToastID + 1
   If switch1.B4JSState Then
       Page.B4JSShowToast("SwitchToast" &amp; ToastID, "green", "Switch is ON", 3000, False)
   Else
       Page.B4JSShowToast("SwitchToast" &amp; ToastID, "green", "Switch is OFF", 3000, False)
   End If
   ' consume the event
   Return True
End Sub

' every method you want to call with a B4JSOn... call MUST return a boolean
public Sub CheckBoxClick() As Boolean
   Dim CheckBox1 As ABMCheckbox 'ignore that it is not initialized, you can't initialize one in B4JS anyway
   CheckBox1.B4JSUniqueKey = "CheckBox001"
   ToastID = ToastID + 1
   If CheckBox1.B4JSState Then
       Page.B4JSShowToast("CheckboxToast" &amp; ToastID, "green", "Checkbox ON", 3000, False)
   Else
       Page.B4JSShowToast("CheckboxToast" &amp; ToastID, "red", "Checkbox OFF", 3000, False)
   End If
   ' consume the event
   Return True
End Sub

' every method you want to call with a B4JSOn... call MUST return a boolean
public Sub RadioClick() As Boolean
   Dim Radio1 As ABMRadioGroup 'ignore that it is not initialized, you can't initialize one in B4JS anyway
   Radio1.B4JSUniqueKey = "Radio001"
   ToastID = ToastID + 1
   Page.B4JSShowToast("RadioToast" &amp; ToastID, "green", "Active Radio = " &amp;  Radio1.B4JSGetActive, 3000, False)
   ' example of setting the active radio
   'If Radio1.B4JSGetActive = 0 Then
   '   Radio1.B4JSSetActive(2)
   'End If
   Return True
End Sub

public Sub RangeOnChange(start As String, Stop As String, ConsumeEvent As Boolean) As Boolean
   Log("B4JS Start: " &amp; start)
   Log("B4JS Stop: " &amp; Stop)
   Return ConsumeEvent
End Sub

#if JAVASCRIPT
function setButtonCSS(id, val) {
   $('#' + id).attr('style', val);
}
#End If

And creating the components in ConnectPage():

public Sub ConnectPage()
   '   connecting the navigation bar
   ABMShared.ConnectNavigationBar(page)

   page.Cell(1,1).AddComponent(ABMShared.BuildParagraph(page,"par1","This demo is practical example of B4JS. It uses the B4JSInputChecker B4JS class.") )

   ' input
   Dim cardInp As ABMInput
   cardInp.Initialize(page, "inp1", ABM.INPUT_TEXT, "Credit Card", False, "")
   cardInp.B4JSUniqueKey = "cardInp001"
   ' special case, it has to pass at least the pressed key (ABM.B4JS_PARAM_INPUTKEY or ABM.B4JS_PARAM_INPUTKEYCODE) to it!
   cardInp.B4JSOnKeyDown("B4JSInputChecker", "InputDown", Array As Object(ABM.B4JS_PARAM_INPUTKEY))
   cardInp.B4JSOnKeyUp("B4JSInputChecker", "InputUp", Array As Object(ABM.B4JS_PARAM_INPUTKEYCODE))
   ' some focus stuff
   cardInp.B4JSOnGotFocus("B4JSInputChecker", "InputGotFocus", Null)
   cardInp.B4JSOnLostFocus("B4JSInputChecker", "InputLostFocus", Null)
   page.Cell(2,1).AddComponent(cardInp)

   ' switch
   Dim switch1 As ABMSwitch
   switch1.Initialize(page, "switch1", "ON", "OFF", False, "")
   switch1.B4JSUniqueKey = "switch001"
   switch1.B4JSOnClick("B4JSInputChecker", "SwitchClick", Null)
   page.Cell(3,1).AddComponent(switch1)

   ' checkbox
   Dim CheckBox1 As ABMCheckbox
   CheckBox1.Initialize(page, "CheckBox1", "B4JS Checkbox", False, "")
   CheckBox1.B4JSUniqueKey = "CheckBox001"
   CheckBox1.B4JSOnClick("B4JSInputChecker", "CheckBoxClick", Null)
   page.Cell(4,1).AddComponent(CheckBox1)

   ' radiogroup
   Dim Radio1 As ABMRadioGroup
   Radio1.Initialize(page, "Radio1", "")
   Radio1.B4JSUniqueKey = "Radio001"
   Radio1.AddRadioButton("radio 0", True)
   Radio1.AddRadioButton("radio 1", True)
   Radio1.AddRadioButton("radio 2", True)
   Radio1.SetActive(1)
   Radio1.B4JSOnClick("B4JSInputChecker", "RadioClick", Null)
   page.Cell(5,1).AddComponent(Radio1)

   ' range
   Dim range As ABMRange
   range.Initialize(page, "range", 10, 20, 0, 100, 1, "")
   range.B4JSUniqueKey = "range001"
   ' special case, it has to pass at least ABM.B4JS_PARAM_RANGESTART & ABM.B4JS_PARAM_RANGESTOP!

   range.B4JSOnChange("B4JSInputChecker", "RangeOnChange", Array As Object(ABM.B4JS_PARAM_RANGESTART, ABM.B4JS_PARAM_RANGESTOP, True))
   page.Cell(6,1).AddComponent(range)

   ' button
   Dim btnCheck As ABMButton
   btnCheck.InitializeFlat(page, "btnCheck", "", "", "Check", "red")
   btnCheck.Enabled = False
   btnCheck.B4JSUniqueKey = "btnCheck"
   btnCheck.B4JSOnClick("B4JSInputChecker", "CheckInput", Null)
   page.Cell(7,1).AddComponent(btnCheck) 

   ' refresh the page
   page.Refresh
   ' Tell the browser we finished loading
   page.FinishedLoading
   ' restoring the navigation bar position
   page.RestoreNavigationBarPosition
End Sub

As you see, for every component we want to use in a B4JS class, we have to set the B4JSUniqueKey property.

We also define some events (B4JSOn…). You notice some of them have special parameters, e.g. the ABMInputs B4JSOnKeyDown event. This event MUST have at least the parameter ABM.B4JS_PARAM_INPUTKEY or ABM.B4JS_PARAM_INPUTKEYCODE to be able to work.

For the B4JSOnKeyDown event for example, we pass the ABM.B4JS_PARAM_INPUTKEY param. The order of the params is very important!

In our B4JS class, this is our method definition:

public Sub InputDown(Key As String) As Boolean<span id="mce_SELREST_start" style="overflow:hidden;line-height:0;">&#65279;</span>

Here ABM.B4JS_PARAM_INPUTKEY’s value will be put into the Key param.

You can add additional params (if needed) as for example is done in the range.B4JSOnChange event declaration.

This concludes the tutorial. ABM 4.25 is now available for download for all donators in the feedback app!

Alwaysbusy

Click here to Donation and support ABMaterial

B4X: ABMaterials B4JS – 06 The UI Connection

B4JSTut06

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

Click here to Donation and support ABMaterial

B4X: ABMaterials B4JS – 05 JSON & Ajax Calls

B4JSTut05

I’ve put JSON and Ajax calls in one tutorial, as in many cases they go together anyway.

JSON
The JSONParser and JSONGenerator in B4JS are almost identical as their B4X versions. The only method not supported (yet) is NextValue. To be honest, I haven’t needed it in any of my projects yet so if someone knows a good example of its usage, please let me know.

This example is based on Erels example, without the reading from file part as this is not supported in B4JS.

public Sub TestJson()
   ' String -> JSON
   Dim JSON As JSONParser
   JSON.Initialize($"{"menu": {
  "id": "file",
  "value": "File",
  "popup": {
    "menuitem": [
      {"value": "New", "onclick": "CreateNewDoc()"},
      {"value": "Open", "onclick": "OpenDoc()"},
      {"value": "Close", "onclick": "CloseDoc()"}
    ]
  }
}}"$)

   ' exploring the JSON tree
   Dim Map1 As Map
   Map1 = JSON.NextObject
   Dim m As Map 'helper map for navigating
   Dim MenuItems As List
   m = Map1.Get("menu")
   m = m.Get("popup")
   MenuItems = m.Get("menuitem")
   For i = 0 To MenuItems.Size - 1
       m = MenuItems.Get(i)
       Log(m.Get("value"))
   Next

   ' manipulating and generating a new JSON file
   Dim Data As List
   Data.Initialize
   Data.Add(1)
   Data.Add(2)
   Data.Add(3)
   Data.Add(Map1) 'add the previous map loaded
   Dim JSONGenerator As JSONGenerator
   JSONGenerator.Initialize2(Data)

   Log(JSONGenerator.ToPrettyString(2))
End Sub

The end result is this:

New
Open
Close
[
  1,
  2,
  3,
  {
    "menu": {
      "id": "file",
      "value": "File",
      "popup": {
        "menuitem": [
          {
            "value": "New",
            "onclick": "CreateNewDoc()"
          },
          {
            "value": "Open",
            "onclick": "OpenDoc()"
          },
          {
            "value": "Close",
            "onclick": "CloseDoc()"
          }
        ]
      }
    }
  }
]

AJAX Calls
In many cases, when you call a REST API you receive a JSON string. We can use what we’ve learned in the previous topic to retrieve the data we want.

Making an AJAX call (to your own B4J server or an external one) can be done like this. The example is getting a test json file from JSONPlaceholder, a Fake Online REST API for testing and protoyping (https://jsonplaceholder.typicode.com).

Note: because I’m running this test locally, I use ‘jsonp’ as the dataType otherwise Chrome will not allow a cross-domain call.

Page.B4JSCallAjax("myJob1", "https://jsonplaceholder.typicode.com/posts/1", "GET", "jsonp", "" , "B4JSCalculateDistance")

the first parameter is the ‘jobID’ (somewhat like you are used to with JobDone in B4X)
The last parameter is where you want to get the events back (an empty string returns to the ABM Page class, else to the B4JS class name specified)

In the ABM Page class we can do something similar, but the events are always returned to the server (ABM Page class)

Page.CallAjax("myJob1", "https://jsonplaceholder.typicode.com/posts/1", "GET", "jsonp", "")

There are couple events we can use:

Note: in this example I’m using the B4JS versions:

Page_AjaxResult and Page_B4JSAjaxResult

Sub Page_B4JSAjaxResult(uniqueId As String, result As String) As Boolean
   Log("Result from: " & uniqueId & " = " & result)
   Dim JSON As JSONParser
   JSON.Initialize(result)
   Dim Map1 As Map
   Map1 = JSON.NextObject
   Log("UsedID: " & Map1.Get("userId"))
   ' if True, it will not call the Page_ajaxResult declared in the Page class!
   Return True
End Sub

Page_AjaxError and Page_B4JSAjaxError

Sub Page_B4JSAjaxError(uniqueId As String, error As String)
   Log("We got an error: " & error)
End Sub

The result is what we would expect:

Result from: myJob1 = {"userId":1,"id":1,"title":"sunt aut facere repellat provident occaecati excepturi optio reprehenderit","body":"quia et suscipit\nsuscipit recusandae consequuntur expedita et cum\nreprehenderit molestiae ut ut quas totam\nnostrum rerum est autem sunt rem eveniet architecto"}
UsedID: 1

Notice our ‘myJob1’ identifier we used in the CallAjax method.

This concludes the basic B4X to B4JS tools. In the next tutorial(s) we’ll dive into how to use this together with ABMaterial.

Alwaysbusy

Click here to Donation and support ABMaterial

B4X: Abmaterials B4JS – 04 Running Javascript on the server side

B4JSTut04

As we have seen in our previous tutorial, we can call B4JS subs and JavaScript subs from withing our B4JS class and also from within our ABM page class.

But what if we could call our JavaScript validateCCNum from our B4J server, even if no browser is connected to our server?

To do this, me must first ‘load’ our B4JS classes into our B4J app. They are loaded onto our server.
This code is placed in our Main AppStart() method.

' we are loading our B4JS.  In the final parameter, we can even load other .js files: Array as String("myJS1.js", "myJS2.js")
   ABM.B4JSLoadOnServer(File.DirApp & "/www/js/", Null)

Note 1: we are NOT using a browser on our server side.
Note 2: this does also mean that this engine can NOT use any DOM JavaScript code (yet)!

First method: we are calling a pure JavaScript method (validateCCNum):

Dim CardNumber As String = "5105105105105100"

Dim myB4JClassVar As B4JSServerVariable
' a pure javascript function does not belong to a B4JS class, so we pass an empty string
myB4JClassVar.Initialize("myVar", "")

Dim isValid As Boolean = myB4JClassVar.B4JSRunMethod("validateCCNum", Array As Object(CardNumber))
If isValid Then
       Log("Server Card '" & CardNumber & "' is a valid card. Please continue...")
Else
       Log("Server Card '" & CardNumber & "' is NOT valid. Please check the number...")
End If

Second method: we are calling our own B4JS sub CheckCard:

Dim myB4JClassVar2 As B4JSServerVariable
' Our CheckCard method DOES belong to our B4JSCalculateDistance class, so pass it
myB4JClassVar2.Initialize("myVar2", "B4JSCalculateDistance")

myB4JClassVar2.B4JSRunMethod("CheckCard", Array As Object(CardNumber))

The result (all in the B4J log of course as we do not have a browser):

' from method 1
Server Card '5105105105105100' is a valid card. Please continue...
' from method 2
Card '5105105105105100' is a valid card. Please continue...<span id="mce_SELREST_start" style="overflow:hidden;line-height:0;"></span>

So what happens here? We create a new calculator variable: B4JSServerVariable
This variable is aware of all the B4JS/JavaScript we have in our app (because of the ABM.B4JSLoadOnServer method).

Now we can use this variable to run a JavaScript method (a native one, or one of our own written in B4JS). For the latter, we do need to specify the B4JS class the method is defined in.

This system could have great potential in the future. Why not acting as a mini NodeJS server… But we’ll see! ;)

Alwaysbusy

Click here to Donation and support ABMaterial

B4X: ABMaterials B4JS – 03 Inline Javascript

B4JSTut03

This is another one of those fantastic B4X features that I wanted in B4JS: Inline Javascript.

Just like its big brothers, you can easily put javascript functions in a B4JS class. This last part is important: you can ONLY declare it in a B4JS class. But, as we will see further, you will be able to call the method in your normal ABM Page class.

So, suppose we found a nice method in Javascript to check a credit card. In your B4JS class, you can use #If JAVASCRIPT and #End If to define a javascript region. You can add multiple methods in one block if you want.

Public Sub InitializeB4JS
   Page.B4JSRunMethod("B4JSCalculateDistance", "cHECKCard", Array As String("5105105105105100"))
   Page.B4JSRunMethod("B4JSCalculateDistance", "CheckCard", Array As String("111111"))
End Sub

public Sub CheckCard(cardNumber As String)
   Dim isValid As Boolean = Page.B4JSRunInlineJavascriptMethod("validateCCNum", Array As Object(cardNumber))
   If isValid Then
       Log("Card '" & cardNumber & "' is a valid card. Please continue...")
   Else
       Log("Card '" & cardNumber & "' is NOT valid. Please check the number...")
   End If
End Sub
#If JAVASCRIPT
function validateCCNum(ccnum) {
   var ccCheckRegExp = /[^\d\s-]/;
    var isValid = !ccCheckRegExp.test(ccnum);
    var i;
    if (isValid) {
        var cardNumbersOnly = ccnum.replace(/[\s-]/g,"");
        var cardNumberLength = cardNumbersOnly.length;
        var arrCheckTypes = ['visa', 'mastercard', 'amex', 'discover', 'dinners', 'jcb'];
        for(i=0; i<arrCheckTypes.length; i++) {
            var lengthIsValid = false;
            var prefixIsValid = false;
            var prefixRegExp;
            switch (arrCheckTypes[i]) {
                case "mastercard":
                    lengthIsValid = (cardNumberLength === 16);
                    prefixRegExp = /^5[1-5]/;
                    break;
                case "visa":
                    lengthIsValid = (cardNumberLength === 16 || cardNumberLength === 13);
                    prefixRegExp = /^4/;
                    break;
                case "amex":
                    lengthIsValid = (cardNumberLength === 15);
                    prefixRegExp = /^3([47])/;
                    break;
                case "discover":
                    lengthIsValid = (cardNumberLength === 15 || cardNumberLength === 16);
                    prefixRegExp = /^(6011|5)/;
                    break;
                case "dinners":
                    lengthIsValid = (cardNumberLength === 14);
                    prefixRegExp = /^(300|301|302|303|304|305|36|38)/;
                    break;
                case "jcb":
                    lengthIsValid = (cardNumberLength === 15 || cardNumberLength === 16);
                    prefixRegExp = /^(2131|1800|35)/;
                    break;
                default:
                    prefixRegExp = /^$/;
            }
            prefixIsValid = prefixRegExp.test(cardNumbersOnly);
            isValid = prefixIsValid && lengthIsValid;

            // Check if we found a correct one
            if(isValid) {
                break;
            }
        }
    }

    if (!isValid) {
        return false;
    }

    // Remove all dashes for the checksum checks to eliminate negative numbers
    ccnum = ccnum.replace(/[\s-]/g,"");
    // Checksum ("Mod 10")
    // Add even digits in even length strings or odd digits in odd length strings.
    var checksum = 0;
    for (i = (2 - (ccnum.length % 2)); i <= ccnum.length; i += 2) {
        checksum += parseInt(ccnum.charAt(i - 1));
    }

    // Analyze odd digits in even length strings or even digits in odd length strings.
    for (i = (ccnum.length % 2) + 1; i < ccnum.length; i += 2) {
        var digit = parseInt(ccnum.charAt(i - 1)) * 2;
        if (digit < 10) {
            checksum += digit;
        } else {
            checksum += (digit - 9);
        }
    }

    return (checksum % 10) === 0;
}
#End If

The result in the browsers log:

Card '5105105105105100' is a valid card. Please continue...
Card '111111' is NOT valid. Please check the number...

Pretty cool no? :)

A couple of things we see here in the code (besides the #if JAVASCRIPT part.

1. Calling a javascript function in your B4JS code using Page.B4JSRunInlineJavascriptMethod:

Dim isValid As Boolean = Page.B4JSRunInlineJavascriptMethod("validateCCNum", Array As Object(cardNumber))

It is VERY important (unlike we are used in B4J, that the method name (here validateCCNum) matches the case. e.g. VAlidateCCNum will NOT work!

2. Calling a B4JS function in your B4JS code using Page.B4JSRunMethod:

Page.B4JSRunMethod("B4JSCalculateDistance", "cHECKCard", Array As String("5105105105105100"))

And we’re back on familiar B4X ground :) The case of the method (or class) does not matter as all is lowercased anyway. Why do we have to mention the class, I hear you think. Well this is because we can call a method from ANOTHER B4JS class too!

And moreover, we can also call these methods in our normal ABM webpage!

sub ConnectPage()
 ...
' method 1: calling our own B4JS sub and handeling the result on thebrowser side
   page.B4JSRunMethod("B4JSCalculateDistance", "cHECKCard", Array As String(CardNumber))

   ' method 2: directly calling the Javascript function and handeling the result on the server side
   Dim isValid As Boolean = page.B4JSRunInlineJavascriptMethod("validateCCNum", Array As Object(CardNumber))
   If isValid Then
       Log("Server Card '" & CardNumber & "' is a valid card. Please continue...")
   Else
       Log("Server Card '" & CardNumber & "' is NOT valid. Please check the number...")
   End If
   ...
End Sub

So the result of method 1 (in the Browser log):

Card '5105105105105100' is a valid card. Please continue...
Card '111111' is NOT valid. Please check the number...

And the result of method 2 (in the B4J log):

Server Card '5105105105105100' is a valid card. Please continue...

In the next tutorial (04 – Running Javascript on the server side) I’ll show how you can even run this on the server side, not even needing a browser open. :p

A final note: #if JAVASCRIPT regions do not really belong to the class. They are shared between all B4JS classes you create.

Happy programming!

Alwaysbusy

Click here to Donation and support ABMaterial

B4X: ABMaterials B4JS – 02 Core Functions

B4JS02

I can be short here: all the Core functions in B4J that are mentioned in the B4JS Introduction can be used. So, on a Sunday morning, some light reading which goes perfectly together with a nice breakfast…

Lets look a little into some topics.

Smart String Literal:

This is such an extraordinary B4X feature that I definitely wanted at least some kind of support for it.

An example:

Sub Class_Globals
   Dim myString As String
   Dim myGlobalName As String = "GlobalAlain"
End Sub

'Initializes the object. You can NOT add parameters.
Public Sub InitializeB4JS()
   Dim myName As String = "Alain"

   ' smartstrings (does not support date, time or xml function
   myString = $"Hello planet "B4X"! This is a test
    from ${myName} and ${myGlobalName}"$

   Log("myString.Contains('planet'): " & myString.Contains("planet"))
End Sub

The result in the browsers log:

Hello planet "B4X"! This is a test
    from Alain and GlobalAlain

myString.Contains('planet'): true

As you can see, it can handle quotes in the string, and variables.  It does not support the special $DateTime{}, $Date{}, $Time{} and $xml{} tags.  But you can make a workaround for it by putting it in a variable first:

Dim myDate as String = DateTime.Date(DateTime.Now)
Dim myTime as String = DateTime.Time(DateTime.Now)

Dim myString as String = $"Here is the current date: ${myDate} and the current time: ${myTime}"$

The result in the console is something like this:

Here is the current date: 03/11/2018 and the current time: 08:25:37

Timer:

When using a timer, you must keep in mind that it runs in the browser.  This means, even when your server is down, your timer will keep running.

The usage is exactly as the B4J counterpart:

Sub Class_Globals
   Private lblTimer As Timer

   Private Page As ABMPage 'ignore, just to be able to run ABMPage functions
   Private ToastID As Int
   Private ABM As ABMaterial 'ignore, just to be able to use ABM constants
End Sub

' Initializes the object. You can NOT add parameters and MUST be called InitializeB4JS.
' is automatically called when the class is created in Javascript
Public Sub InitializeB4JS()
   lblTimer.Initialize("lblTimer", 5000)
End Sub

Sub lblTimer_Tick
       ToastID = ToastID + 1
       Page.B4JSShowToast("TimerToast" & ToastID, "green", "I'm shown in the timer without the server every 5 seconds!", 3000, False)
End Sub

StringBuilder:

Although the Smart String Literal is still better to use, B4JS does also support the StringBuilder.

Dim sb As StringBuilder
sb.Initialize
sb.Append("0123456789").Append(CRLF).Append("0123456789")
Log("sb.ToString: " & sb.ToString)
Log("Length: " & sb.Length)  ' should show 21, including the return
sb.Insert(2,"X")
Log("sb.ToString: " & sb.ToString)
sb.Remove(2,3)
Log("sb.ToString: " & sb.ToString)

The result output in the Browser console:

sb.ToString: 0123456789
0123456789
Length: 21
sb.ToString: 01X23456789
0123456789
sb.ToString: 0123456789
0123456789

This concludes this tutorial.  As a B4X developer, there are not many differences between B4JS and the familiar B4X syntax, which makes it very easy to get started with.

The next tutorial will dig a bit deeper into using inline JavaScript, another feature that speeds up development considerably.

Alwaysbusy

Click here to Donation and support ABMaterial

B4X: Writing JavaScript with ABMaterials B4JS

B4JS

After a delay of more of a year working on other ABM stuff, I finally got back on track with B4JS. I will make a series of tutorials explaining how it works. B4JS is part of the ABMaterial library 4.25 which is going to be released next week.

INTRODUCTION

One way to look at B4JS is as some kind of new platform in B4X, that uses a very similar syntax as B4J. At runtime, the B4J source code is transpiled to pure JavaScript. The generated source code can then be used in e.g. an ABMaterial Web App, or maybe even as a base for another JavaScript library.

A typical B4JS class could look like this:

Sub Process_Globals
   Dim myString As String
   Public pubMap As Map

   Dim timer As Timer
   Dim myGlobalName As String = "GlobalAlain"
End Sub
'Initializes the object. You can NOT add parameters.
Public Sub InitializeB4JS()
   pubMap.Initialize

   Dim myName As String = "Alain"
   ' smartstrings do not support date, time or xml functions
   myString = $"Hello planet "B4X"! This is a test
    from ${myName} and ${myGlobalName}"$
   Log("myString.Contains('planet'): " & myString.Contains("planet"))
   Dim myLocalInt As Int = 15
   myLocalInt = myLocalInt * myString.Length

   LogMe("15 x the length of " & myString & " = " & myLocalInt)

   For i=0 To 50
       myLocalInt = myLocalInt + 2
       Select Case myLocalInt
           Case 20, 40
               Log(i)
               pubMap.Put("key" & i, i)
           Case Else
               Log("less than 20")
       End Select
   Next

   timer.Initialize("timer", 1000)
   timer.Enabled = True

   Dim sb As StringBuilder
   sb.Initialize
   sb.Append("lijn 1").Append(CRLF).Append("lijn 2")
   Log(sb.ToString)
   Log(sb.Length)
   sb.Insert(2,"X")
   Log(sb.ToString)
   sb.Remove(2,3)
   Log(sb.ToString)
End Sub
Private Sub Timer_Tick
   timer.Enabled = False
   Log("timer ticking")
   If timer.Enabled = False Then
       timer.Interval = timer.Interval + 1000
       Log("timer new interval: " & timer.Interval)
       timer.Enabled = True
   End If
End Sub

Looks very familiar, no? :)

Having the browser doing some stuff using JavaScript can have some big advantages by relieving some pressure from your server (checking if a form is filled in correctly, or changing the visibility of an ABMContainer if a user clicks a button).

But it also demands a great responsibility from the programmer not to expose to much information to the user. Never, ever write sensitive stuff like SQL, passwords etc in B4JS!

Another advantage is being able to expose some events (like the KeyUp/KeyDown events in an ABMInput field). They are deliberately omitted in ABM, because such events could potentially kill your server. But in Javascript, we could use them e.g. to check if the user could entry numbers, or if fits an email mask.

Having a Timer running on the browser side can also be handy.

First an overview of the syntax B4JS can handle:

OVERVIEW

Core library
Variable types:

Bit:

String:

List:

Map:

StringBuilder:

DateTime:

Keywords:

Operators:

Control Structures:

Timer:

Smart String Literal:

JSON library
JSONParser:

JSONGenerator:

ABMaterial library (as of 2018/03/10, for the latest list check the B4X forum):
ABMLabel:

EVENTS: B4JSOnClick, B4JSOnMouseEnter, B4JSOnMouseLeave

ABMInput:

ABMContainer:

ABMSwitch:

ABMCheckbox:

ABMRadioGroup:

ABMButton:

ABMPage:

ABMRow:

B4JSUniqueKey

EVENTS: B4JSOnClick, B4JSOnMouseEnter, B4JSOnMouseLeave

ABMCell:

B4JSUniqueKey

EVENTS: B4JSOnClick, B4JSOnMouseEnter, B4JSOnMouseLeave

TUTORIALS

In the next following days, I would like to cover the following topics:

01. Getting started
02. Core functions
03. Inline JavaScript
04. Running JavaScript on the server side (mini NodeJS?)
05. JSON & Ajax Calls
06. The UI (ABMaterial) connection

Alwaysbusy

Click here to Donation and support ABMaterial

B4J: Future steps for ABMaterial

evolution

As ABMaterial has grown to a production ready framework for WebApps using B4J, I recently started a poll on the B4X forum to find out what could be the next steps in its evolution.

I came up with 3 things to do. They would require about the same develop time, but it could be useful to see what the actual users of ABMaterial would like me to work on next.

The possible choices were:

1. ABMXPlay
This is the component you have seen some demos of and will be more for game developers. It still needs a lot of attention before it can be released.

2. B4JS
The Basic to Javascript transpiler is now in its very early stages. It is a tough one to write without finishing ABMXplay first, as the ABMXPlay component would be the first to really benefit from it. The B4J core functions have been covered, but big parts of all ABMComponents need to be refactored to start using B4JS.

3. ABMaterialAbstractDesigner
A full blown Abstract Visual Designer like the one in B4J. This is NOT a code generator but the real deal. Code generators may look nice at first sight, but  have a huge disadvantage: they go one way. ABMAbstractDesigner would work like the B4X ones: you create a layout, develop, change the layout, continue to develop and so on without losing what you have written in code. I’ll use the grid designer I’ve shown as a proof of concept in another post + the possibility to put ABMComponents on them. I’ve done some tests this weekend and it would work very similar as the other B4X tools.

4. Other (please specify)

The poll is now nearly ended and besides some interesting messages with excellent pointers, it was quite obvious from the beginning that the ABMaterialAbstractDesigner (will need a working title for this project, lets call it ABMAD for now…) would be the next part I’ll be working on. (Almost 75% in the poll at the time of writing).  This was also my preferred choice as one user did not really have the patience to wait for me and started to write some kind of code generator.  However, his limited knowledge of the ABMaterial framework has the potential of starting to reflect bad on ABMaterial itself so it is time for me to jump in.

Making ABMAD will happen in a couple of logical steps:

  1. Making the ABMaterial components ready for a designer.  This will ask for some (minor) changes to them so they can be serialized and expose their properties to the designer.
  2. Making a grid builder.  This is the biggest step new users have to overcome nowadays.
  3. Making a properties view for the components.
  4. Drag/Drop facilities to add components to specific cells
  5. Let the ABMaterial library communicate with the Designer output in a similar way as B4X does now.

This will not happen overnight, but the benefits for ABMaterial programmers will be substantial.  This will NOT be a simple code generator. ABMAD will work bidirectional.  And you’ll be able to make layouts in ABMAD and mix them with manually written code like you can do in B4X/ABMaterial now.

Note: To avoid future misunderstandings, the license of ABMaterial 2.0 will include the following statement:

4. YOU AGREE NOT TO DISTRIBUTE, FOR A FEE, AN APPLICATION USING THE LIBRARY THAT, AS ITS PRIMARY PURPOSE, IS DESIGNED TO BE AN AID IN THE DEVELOPMENT OF SOFTWARE FOR YOUR APPLICATION’S END USER. SUCH APPLICATION INCLUDES, BUT IS NOT LIMITED TO, A DEVELOPMENT IDE OR A B4J SOURCE CODE GENERATOR.

Let me explain. The philosophy of ABMaterial (and myself) is giving anyone the chance to easily build great WebApps without having to spend any money. One of the reasons I picked B4J was because it is free. Those who can spare something, donate to stimulate my continuation on the project and in so help giving other not so fortunate people an equal chance to grow. Any derived development tool or generator that uses ABMaterial must follow this philosophy so the tool must be available with 100% of its features for free (not even a ‘limited’ free version is acceptable).

This has no impact on whatever WebApp you make with ABMaterial, but is solely a protection against some ‘money-grubber’ who sees the potential of the ABMaterial framework and thinks of making some easy money using my engine by simply writing a layer above it. Just to be clear, ‘FEE’ here means ANY form of payment or compensation, donations included.

So now that this is out of the way, I think it’s time to step up to the drawing board and start to make some decisions on how ABMAD will look like.

Fun times ahead!

Alwaysbusy

Click here to Donation and support ABMaterial