[B4X] BANano v2: B4J Abstract Designer (part 1)

DOWNLOAD THE LATEST VERSION OF BANANO:

https://www.b4x.com/android/forum/threads/banano-progressive-web-app-library.99740/#post-627764

INTRODUCTION

BANano v2 has a completely new (better) way of using the B4J Abstract Designer. Now, you can make Custom Views very easy that can be dragged and dropped in the Designer, with its own properties and events. Kudos to @Kiffi for helping me testing and evaluating v2!

There are a couple of ways to use these Custom Views:

1. by putting them on a layout and loading BANAno.LoadLayout
2. by creating them in code
3. NEW: by putting them on a layout and loading them with BANano.Layout

In part 2 of this tutorial, I will cover all three of them. But for now we concentrate on building a Custom View.

The best way to make a bunch of custom views (e.g. for wrapping a CSS library) is by creating a BANanoLibrary: it will keep your code cleaner.

Unlike v1, BANano v2 needs to be a UI B4J project (we do want to use the Abstract Designer after all).

The BANano download contains a wrapper library for the Skeleton CSS library. @tchart has written a wrapper for B4J already, but as this is a very small library with just some basic components, I asked him if he would mind me wrapping it again for learning purposes. Asking was the polite thing to do and he gave me the go-ahead. 🙂

A demo using this BANanoLibrary can be seen here: http://gorgeousapps.com/BANano

For this tutorial, we are going to recreate the SKTextbox component.

GETTING STARTED

Create a new B4J UI project if you haven’t done that yet. Replace the Main source code with the BANano default project code:

#Region Project Attributes
   #MainFormWidth: 600
   #MainFormHeight: 600
   #IgnoreWarnings: 16, 10, 14, 15
#End Region

Sub Process_Globals
   Private BANano As BANano 'ignore
End Sub

Sub AppStart (Form1 As Form, Args() As String)
   ' you can change some output params here
   BANano.Initialize("BANano", "BANanoSkeleton",12)
   BANano.HTML_NAME = "index.html"
 
   BANano.Header.Title="BANano Skeleton"
   BANano.ExternalTestConnectionServer = "http://gorgeousapps.com"
   BANano.Header.AddCSSFile("skeleton-all.min.css")
 
   ' start the build
   #if release
       BANano.BuildAsLibrary()
   #else
       BANano.Build(File.DirApp)
   #end if
 
   ExitApplication
End Sub

' HERE STARTS YOUR APP
Sub BANano_Ready()

End Sub

CREATING A CUSTOM VIEW

1. Adding a new BANano Custom View

NOTE: due to a current limitation of B4J, if you use Uppercase letters in the class name, the events will not show up in the IDE when typing Sub + TAB until you compile it as a library. When you use the library in a project, they will show.

You will see quite some code is prepared for you. On top, the most common events in Javascript have been pre-defined. These are the events you want to expose in the Abstract Designer to the user.

Next are some common properties like ‘Classes’, ‘Style’, ‘Margins’ and ‘Padding’.

Some basic code is also inserted. The initialize and DesignerCreateView method definitions can NOT be changed: you can not add or remove parameters to/from it! This is nothing new, as you can’t do this for a normal B4J Custom component either.

The DesignerCreateView method is where the magic happens, but I get back to this when we need to.

2. Activating events

You can just active an event by uncommenting them. If can also add events that are not in this list. They always have to be in format:

javascripteventname (event As BANanoEvent)

For our textbox, let’s activate the following events:

#Event: Focus (event As BANanoEvent)
#Event: Blur (event As BANanoEvent)
...
#Event: Keydown (event As BANanoEvent)
#Event: KeyUp (event As BANanoEvent)
...
#Event: Change (event As BANanoEvent)

In DesignerCreateView, we have to attach those events:

mElement.HandleEvents("change", mCallBack, mEventName & "_change") ' eventname must be lowercased!
mElement.On("keydown", mCallBack, mEventName & "_keydown") ' must be On because be do not want the default to be cancelled at the point of keydown
mElement.HandleEvents("keyup", mCallBack, mEventName & "_keyup")
mElement.HandleEvents("blur", mCallBack, mEventName & "_blur")
mElement.HandleEvents("focus", mCallBack, mEventName & "_focus")

Note we do use mCallBack here. This is because we want the class that initializes this component to be the one to handle our events.

In some cases, you want to handle events that are not exposed to the user (like a hover). In this case use Me as the callback parameter.

For example from the SKTable custom view:

Elems(0).HandleEvents("mouseenter", Me, "rowmouseenter") ' eventname must be lowercased!
Elems(0).HandleEvents("mouseleave", Me, "rowmouseleave")
...
#Region Internal Events
public Sub RowMouseEnter(event As BANanoEvent)
   Dim row As BANanoElement
   row.Initialize("#" & event.ID)
   row.AddClass("tablehover")
End Sub

public Sub RowMouseLeave(event As BANanoEvent)
   Dim row As BANanoElement
   row.Initialize("#" & event.ID)
   row.RemoveClass("tablehover")
End Sub
#End Region

3. Adding properties

We do want some custom properties on our TextBox:

#DesignerProperty: Key: Label, DisplayName: Label, FieldType: String, DefaultValue: , Description: Label of the TextBox.
#DesignerProperty: Key: Width, DisplayName: Full Width, FieldType: Boolean, DefaultValue: True, Description: Set the width 100%
#DesignerProperty: Key: Text, DisplayName: Text, FieldType: String, DefaultValue: , Description: Text of the TextBox
#DesignerProperty: Key: Placeholder, DisplayName: Placeholder, FieldType: String, DefaultValue: , Description: Placeholder text
#DesignerProperty: Key: Type, DisplayName: Type, FieldType: String, DefaultValue: text, List: text|email|password, Description: Type TextBox

All B4J supported FieldTypes can be used: Int, Float, String, Color, Boolean…

Add some variables to hold the properties:

Private mLabel As String = ""
Private mWidth As Boolean = False
Private mText As String = ""
Private mPlaceHolder As String = ""
Private mType As String = "text"

Now, when we use this component in the Abstract Designer, we want the view to use the ones we have set in the desinger. This is done by getting them from the Props map (Case does matter!).

If Props <> Null Then
       ...
       mLabel = Props.Get("Label")
       mWidth = Props.Get("Width")
       mText = Props.Get("Text")
       mPlaceHolder = Props.Get("Placeholder")
       mType = Props.Get("Type")
End If

In case you want to be able to set these properties in code too e.g. to change the labels text, we create getters and setters. The method name must start with get/set (Lowercased!).

public Sub setLabel(label As String)
   If mElementLabel <> Null Then
       mElementLabel.SetHTML(BANano.SF(label))
   End If
   mLabel = label
End Sub

public Sub getLabel() As String
   Return mLabel
End Sub

4. Building the view with HTML.

In Skeleton CSS, a textbox with a label needs to have a certain structure:

<label for="txt1">Text box</label>
<input placeholder="Enter some text" type="text">

This is simple to do in BANano thanks to B4Js smartstrings:

' preparing some variables
Dim exStyle As String = BuildExStyle
 
Dim extraWidth As String
If mWidth Then
   extraWidth = "u-full-width"
End If

' building the html
mElementLabel = mTarget.Append($"<label id="${mName}label" for="${mName}">${BANano.SF(mLabel)}</label>"$).Get("#" & mName & "label")

mElement = mTarget.Append($"<input id="${mName}" class="${extraWidth} ${mClasses}" style="${exStyle}${mStyle}" placeholder="${mPlaceHolder}" type=${mType}>"$).Children("#" & mName)
mElement.SetValue(mText)

One can keep a reference of the created parts (mElementLabel and mElement), but in some cases you can just grab it when needed. So this could also be written as:

mTarget.Append($"<label id="${mName}label" for="${mName}">${BANano.SF(mLabel)}</label>
               <input id="${mName}" class="${extraWidth} ${mClasses}" style="${exStyle}${mStyle}" placeholder="${mPlaceHolder}" type=${mType}>"$)
mElementLabel.Initialize("#" & mName & "label")
mElement.Initialize("#" & mName)
mElement.SetValue(mText)

It is a personal preference I guess.

Et voila, our View is finished: yes that is it! 🙂
Check out the source code of the Skeleton library in the download to see the whole class.

In part 2 of this tutorial, I’ll show you how to use such a view and make a BANano library out of it.

Alwaysbusy

or

Click here to Donation and support BANano & ABMaterial

Advertisements

[B4X] BANano 1.21 Christmas gift!

INTRO

BANano is a new B4J library to create one-page websites/webapps with (offline) Progressive Web App support. Unlike its big brother ABMaterial, BANano does not rely on any particular framework like Materialize CSS. You will have to write that part yourself, but on the other hand, you have the choice to pick which one.

Why a second framework? Well, from the start ABMaterial was build with a back server in mind. B4J has great support to setup a very powerful jServer to handle web requests. Which does mean all intelligence is at the server side and you have the full power of B4J to do whatever you want (secure database access, serial communication, cache control etc). With B4JS, some of this intelligence could be transferred to the browser side, but the app still needs internet/intranet access so this is as far as it could go.

BANano is a different animal. It can use a Service Worker to ‘install’ the web app in the browser, so it can also work when the user is offline. While ABMaterial builds the page from the server side, BANano builds it from the browser side. This means EVERYTHING you write in B4J is transpiled to JavaScript, HTML and CSS.

But with great power comes great responsibility! Unlike ABMaterial, knowledge of HTML, CSS and to some extend JavaScript is needed to build BANano apps. It makes excellent use of B4X’s SmartStrings to create the HTML part of the app. BANano gives you a set of tools to write your own wrapper around any framework (MiniCSS, Skeleton, Spectre, Bootstrap, …), which then can be easily used to quickly build webapps/websites.

v1.21+: Abstract Designer support:

It is a rather simple and elegant system I think:

1. You set BANanoDesignElement elements in the designer (Add View, Custom view).

Only these properties apply:

2. Each has a Build event where you can do your specific html stuff

3. You load a layout with BANano.LoadLayout. At this time, the bjl file is parsed (as B4J custom components do not support a different parent than main yet, I had to write an alogorithm to find the parent myself) and translated to _Build commands (you will see it in the DemoUI library file).

Sub Class_Globals
End Sub

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

End Sub

Sub Div_Build (el As BANanoDesignElement)
   el.Target.Append($"
<div id="${el.Name.ToLowerCase}" class="${el.Classes}"></div>

"$)
End Sub

Sub Row_Build (el As BANanoDesignElement)
   el.target.Append($"
<div id="${el.Name.ToLowerCase}" class="${el.Classes}"></div>

"$)
End Sub

Sub Column_Build (el As BANanoDesignElement)
   el.target.Append($"
<div id="${el.Name.ToLowerCase}" class="${el.Classes}"></div>

"$)
End Sub

' extra: the level of h;the text
Sub H_Build (el As BANanoDesignElement)
   el.target.Append($"<h${el.Extra(0)} id="${el.Name.ToLowerCase}" class="${el.Classes}">${el.Extra(1)}</h${el.Extra(0)}>"$)
End Sub

Sub P_Build (el As BANanoDesignElement)
   el.target.Append($"

${el.Extra(0)}</p}>"$)
End Sub

Sub Button_Build (el As BANanoDesignElement)
   Dim btn() As BANanoElement = el.target.RenderAppend($"<button id="${el.Name.ToLowerCase}" class="${el.Classes}">${el.Extra(0)}</button>"$,"").Children("#" & el.Name.ToLowerCase)
   ' defining events is very simple. Note that it has to be run AFTER adding it to the HTML DOM!
   ' eventName must be lowercase!
   btn(0).HandleEvents("click", el.ModuleEventHandler, el.Name.ToLowerCase & "_clicked")
End Sub

' extra: image source;alt text
Sub Image_Build (el As BANanoDesignElement)
   el.target.Append($"<img id="${el.Name.ToLowerCase}" src="${el.Extra(0)}" alt="${el.Extra(1)}"/>"$)
End Sub

Using this in a real app is now very simple:

Sub Process_Globals
   Private BANano As BANano 'ignore
   Private Builder As BANanoMiniCSSBuilder '<-------- defining our BANano Library
End Sub

Sub AppStart (Form1 As Form, Args() As String)

   ' you can change some output params here
   BANano.Initialize("BANano", "BANanoMiniCSS",12)
   BANano.HTML_NAME = "banano.html"

   BANano.Header.Title="BANano MiniCSS"
    
   BANano.ExternalTestConnectionServer = "http://gorgeousapps.com"

   BANano.Header.AddCSSFile("mini-nord.min.css")
        
   ' start the build
   BANano.Build(File.DirApp)

   ExitApplication
End Sub

'Return true to allow the default exceptions handler to handle the uncaught exception.
Sub Application_Error (Error As Exception, StackTrace As String) As Boolean
   Return True
End Sub

' HERE STARTS YOUR APP
Sub BANano_Ready()
   Builder.Initialize '<------- initialize the library

   BANano.LoadLayout("#body", "Layout1", Builder, Me) '<--- load the layout
End Sub

Sub MyButton_Clicked(event As BANanoEvent)
   Log("Clicked on " & event.ID)    
   BANano.GetElement("#r3c2").Empty
   BANano.LoadLayout("#r3c2", "SubLayout1", Builder, Me) ' <---- load a sub layout
End Sub

Builder is the library (the one who will handle the Build() events)
Me (the one who will handle e.g. the MyButton_Clicked event: so like all other events except Build)

An example project using the MiniCSS javascript library is included in the zip (both the library and a demo project using it) in the DemoUi folder.

Couple of important notes:
1. Both the library and App must be a B4J UI project, not a console project
2. When you now run BuildAsLibrary, the assets in the Files folder will be zipped to Files.zip, next to the .js and .dependsOn files.

I think this is cool! Very happy with it…

DOWNLOAD: FREE library and some basic demo projects

I wish you all a fantastic year-end end and a very productive and happy new 2019!

Alwaysbusy

Click here to Donation and support BANano & ABMaterial

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