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

Advertisements

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: ABMaterials B4JS – 01 Getting Started

In this first tutorial, we are going to create a killer method to calculate the distance between two GEO locations. There are better ways to do so, but for demos sake…

Creating a B4JS class is very simple. Just use the menu in B4J and add a Standard Class.

'Class module
Sub Class_Globals

End Sub

Public Sub Initialize

End Sub

First thing we MUST do is renaming the Initialize method to InitializeB4JS. This is because the transpiler uses this method name to determine it has to translate this class to JavaScript.

This InitializeB4JS method can NOT have parameters!

Now we can start adding our code, just as usual in B4J. As almost the whole Core library of B4J is available, this is pretty forward. Except from the ABM methods, this could easily be seen as B4J.

'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
   Dim ToKM As Double = 1.609344
   Dim ToMiles As Double = 0.8684

   ' 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 CalcDistance(Lat1 As Double, Lon1 As Double, Lat2 As Double, Lon2 As Double, Unit As String)
   Dim theta As Double
   Dim Distance As Double
   theta = Lon1 - Lon2
   Distance = Sin(deg2rad(Lat1)) * Sin(deg2rad(Lat2)) + Cos(deg2rad(Lat1)) * Cos(deg2rad(Lat2)) * Cos(deg2rad(theta))
   ' logging some intermediate value
   Log("Distance = " & Distance)
   Distance = ACos(Distance)
   Distance = rad2deg(Distance)
   ' logging some intermediate value
   Log("Distance = " & Distance)
   Distance = Distance * 60 * 1.1515
   ' if we would use Page.Msgbox here, we would see in the logs an error: msgbox is NOT supported in B4JS!
   ' we must use the B4JS equivalent method Page.B4JSMsgbox
   Select Case Unit.ToUpperCase
       Case "K"
           Page.B4JSMsgbox("msgbox", "The distance is " & (Distance * ToKM) & " kilometers!", "Tutorial", "OK", False, ABM.MSGBOX_POS_CENTER_CENTER, "")
       Case "N"
           Page.B4JSMsgbox("msgbox", "The distance is " & (Distance * ToMiles) & " miles!", "Tutorial", "OK", False, ABM.MSGBOX_POS_CENTER_CENTER, "")
       Case Else
           Page.B4JSMsgbox("msgbox", "No idea what you are doing :-)", "Tutorial", "OK", False, ABM.MSGBOX_POS_CENTER_CENTER, "")
   End Select
End Sub

' some helper methods
Sub deg2rad(Deg As Double) As Double
   Return Deg * cPI / 180
End Sub

Sub rad2deg(Rad As Double) As Double
   Return Rad * 180 / cPI
End Sub

VERY IMPORTANT!!!
Depending on how you declare the variable in Class_Globals, a variable is shared between class instances or not:

This concept becomes very important when we start using ABMComponents because when you attach a B4JSOn… event to an ABMComponent, it gets its own instance of your class. The Public/Dim variables will be shared among all the components using this B4JS Class

To use our new method, I’ll make a button in ConnectPage() on the ABM page (this will be explained in a future tutorial) and when we click, we do a calculation:

Dim btn As ABMButton
btn.InitializeFlat(page, "btn", "", "", "Calculate", "")
' B4JSUniqueKey is explained in a later turorial
btn.B4JSUniqueKey = "btn001"
' define the B4JS OnClickedEvent
btn.B4JSOnClick("B4JSCalculateDistance", "CalcDistance", Array As Object(32.9697, -96.80322, 29.46786, -98.53506, "K"))
page.Cell(2,1).AddComponent(btn)

Alternative, not using an ABMButton but calling our method directly:

page.B4JSRunMethod("B4JSCalculateDistance", "CalcDistance", Array As Object(32.9697, -96.80322, 29.46786, -98.53506, "K"))

So let’s check our results (click to enlarge):

B4JSTut1a

1. In the browsers log we see our two intermediate log() calls.
2. the solution to our call is shown in a message box.

But in the B4J log we also see that the normal btn_Click event is raised! That is not what we want.

To avoid this, we make a simple change to our CalcDistance method. We return a boolean true: this is saying ‘consume the click on the browser side and don’t go to the server‘.

public Sub CalcDistance(Lat1 As Double, Lon1 As Double, Lat2 As Double, Lon2 As Double, Unit As String) As Boolean
   ...
   ' important, if we do not want to raise the servers btn_click events, we must return true
   Return True
End Sub

And hooray, our server is not contacted any more! :)

B4JSTut1b

Ultimate proof we are not contacting the server for this code. I’ve stopped the server app and I can still use the button:

B4JSTut1c

This concludes the first tutorial.

For those interested in the JavaScript generated for our class, here it is:

var_tokm=1.609344;
var _tomiles=0.8684;
var _abm;
var _page;
function b4js_b4jscalculatedistance() {
     var self;
     this.initializeb4js=function(){
          self=this;
          try {
          }
          catch(err) {
               console.log(err.message + ' ' + err.stack);
          }
     };
     this.calcdistance=function(_lat1,_lon1,_lat2,_lon2,_unit){
          try {
               var _theta=0;
               var _distance=0;
               _theta = _lon1-_lon2;
               _distance = (Math.sin(self.deg2rad(_lat1)))*(Math.sin(self.deg2rad(_lat2)))+(Math.cos(self.deg2rad(_lat1)))*(Math.cos(self.deg2rad(_lat2)))*(Math.cos(self.deg2rad(_theta)));
               console.log("Distance = "+_distance);
               _distance = (Math.acos(_distance));
               _distance = self.rad2deg(_distance);
               console.log("Distance = "+_distance);
               _distance = _distance*60*1.1515;
               switch ("" + _unit.toUpperCase()) {
                    case "" + "K":
                         var _b4js_returnname="msgbox";
                         b4js_msgbox("default","Tutorial","The distance is "+(_distance*_tokm)+" kilometers!","OK",'swal2pos-cc', false);;
                         break;
                    case "" + "N":
                         var _b4js_returnname="msgbox";
                         b4js_msgbox("default","Tutorial","The distance is "+(_distance*_tomiles)+" miles!","OK",'swal2pos-cc', false);;
                         break;
                    default:
                         var _b4js_returnname="msgbox";
                         b4js_msgbox("default","Tutorial","No idea what you are doing :-)","OK",'swal2pos-cc', false);;
                         break;
               }
               self.testjson();
               callAjax("https://jsonplaceholder.typicode.com/posts/1","GET","jsonp", "","myJob1", true,"b4jscalculatedistance");
               return true;
          }
          catch(err) {
               console.log(err.message + ' ' + err.stack);
          }
     };

     this.deg2rad=function(_deg){
          try {
               return _deg*Math.PI/180;
          }
          catch(err) {
               console.log(err.message + ' ' + err.stack);
          }
     };
     this.rad2deg=function(_rad){
          try {
               return _rad*180/Math.PI;
          }
          catch(err) {
               console.log(err.message + ' ' + err.stack);
          }
     };
}

In the text tutorial we are going to see how we can use inline JavaScript snippets within our B4JS classes!

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

B4X: Responsive containers in ABMaterial 4.25

ABMDragonfly4.00

Next to the normal responsive page behaviour, in ABMaterial 4.25 for B4J, you will be able to do this with containers too!

Let’s watch it at work first:

This may not sound super special, but it’s a biggie! There are very few frameworks out there that can do this, so I’m very proud of this one.

Nice but that must ask for a lot of control code, no?“, I hear you ask.
Well, here it is:

gridContainer.IsResponsiveContainer = True

Done! 🙂

Just by setting gridContainer.IsResponsiveContainer = True, you just made the ABMContainer device responsive just like a page. This means you can from now on actually create controls that behave differently depending on the device you are on.

For example, setting this grid on a container, the last 5 cells will wrap differently on a phone, tablet or desktop:

gridContainer.AddRows(1,True,"").AddCells12(1,"")
gridContainer.AddRows(1,True,"").AddCells12(1,"")
gridContainer.AddRows(1,True,"").AddCellsOSMP(5,0,0,0,12,6,4,0,0,0,0,"")

I also added some powerful tools to adjust the layout of your WebApp according to the device you are on:

Page.GetCurrentPageSize:
Returns the current page size (phone, tablet, desktop). You can use this method e.g. in ConnectPage to determine the state of the current page size and act accordingly.

public Sub ConnectPage()
   Log("Current page size: " & page.GetCurrentPageSize)
   ...
End Sub

An event Page_SizeChanged will return the same value in the ‘current’ parameter if the user changes the window size. (It also returns the ‘previous’ state).

Note that this event is NOT raised at load time! Use Page.GetCurrentPageSize to check that.

Sub Page_SizeChanged(previous As String, current As String)
   Log(previous & " -> " & current)
   Select Case current
       Case "phone"
           gridContainer.MarginLeft = "0px"
           gridContainer.SetFixedWidth("100%")
       Case "tablet"
           gridContainer.MarginLeft = "210px"
           gridContainer.SetFixedWidth("calc(100% - 210px)")
       Case "desktop"
           gridContainer.MarginLeft = "210px"
           gridContainer.SetFixedWidth("calc(100% - 610px)")
   End Select
   gridContainer.Refresh
End Sub

Containers on a fixed position:
Next to setting a fixed with and height of an ABMContainer, you can now also set a fixed position. This can be handy to create ‘floating’ sidebars for example.

compContainer.SetFixedPosition("0px", "", "56px", "")

Params: left, right, top,bottom

Set a parameter to empty string when you don not want to set it.

These 3 new features used together give you an immense power over how your WebApp should behave depending on the device it is running on, with very little effort.

Another year has passed:

2017 has been a great year for B4X and ABMaterial!

B4X has grown to be a very powerful player in develop land.  The already productive RAD IDE has received a couple of very innovative features to help you even more with your app development. Anywhere Software is always right on top of all the new stuff Android, iOS and all the other supported platforms throw at us.  And being stable and reliable makes this a unique environment. The growth of the forum and the quality of the messages/responses are the real proof of this.  Thanks a lot Erel for this beautiful tool.  It is without a doubt the best cross-platform tool one can use for the moment!

And ABMaterial is lucky to be able to stand on the shoulders of this giant.  ABM has surpassed my biggest expectations this year.  It is great to see a lot of you are using it to make amazing webapps and even can make a living out of it.  This was definitely the ‘year of speed‘.  Very few frameworks can match ABM when it comes to develop and execution time. And there is a lot more in the pipeline for the upcoming months!

Thank you all for following my blog!  You inspire me every day to push the limits of the tools and frameworks I make.

Rest me only to wish you all the very best and I hope to see you back in 2018!

Alwaysbusy

Click here to Donation and support ABMaterial

B4X: Custom Icons in ABMaterial 4.25

customicons

A much asked feature has been to integrate custom icons in ABMaterial for B4J. In version 4.25, this will be possible!

Couple of notes first:
1. The already build-in font icons (font awesome, google icons) are a lot faster to load, so consider them first
2. Some basic CSS knowledge will be needed (margins, padding, fill)
3. Custom icons MUST be named starting with abm-

There are three ways to load custom icons in ABM:

1. SVG
These type of icons MUST be loaded in BuildPage. This is because, when used, they link to the SVG tags (contrary to embedded SVG, see further). If used multiple times, it will make your HTML code smaller as the SVG code is only included once in the HTML.

page.AddSVGIcon("abm-si-maki-basketball-11", $"<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" style="width:0;height:0;position:absolute;overflow:hidden;">
     <defs><symbol viewBox="0 0 11 10.54083251953125" aria-labelledby="basi-maki-basketball-11-title" id="abm-si-maki-basketball-11"><title id="basi-maki-basketball-11-title">icon basketball-11</title><path d="M11 1a1 1 0 1 1-2 0 1 1 0 0 1 2 0zM4.5 3a1.5 1.5 0 1 0 0-3 1.5 1.5 0 0 0 0 3zm3.89 6.69L6 6.59V4.5h.5a.49.49 0 0 0 .41-.23l1.5-2A.49.49 0 0 0 8.5 2a.5.5 0 0 0-.86-.34L6.25 3.5H2.5a.5.5 0 0 0-.36.16l-2 2.54a.49.49 0 0 0-.14.3.5.5 0 0 0 .5.5.49.49 0 0 0 .41-.23L2.74 4.5H3v2.09L.61 9.69a.5.5 0 0 0 .39.81.49.49 0 0 0 .41-.23L3.94 7h1.12l2.52 3.27A.5.5 0 0 0 8.5 10a.49.49 0 0 0-.11-.3v-.01z"/></symbol> </defs>
   </svg>"$, 24,24,"margin-top: 12px;fill: black")

As you can see, in here we have also to declare a margin-top and a fill color in CSS. The name for our icon is ‘abm-si-maki-basketball-11′.

Icons like this typically have an SVG tag looking like this (hidden, width & height = 0, ALL properties are required!):

<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" style="width:0;height:0;position:absolute;overflow:hidden;">...</svg>

10000+ icopns of this type can be found here: https://leungwensen.github.io/svg-icon/

2. Embedded SVG
These type of icons can be loaded at runtime (e.g. in ConnectPage) as the SVG tag code will be included in the html. This also means if you use the same icon multiple times, the full SVG code will be added multiple times too!

page.AddEmbeddedSVGIcon("abm-aardbei", $"<svg xmlns="http://www.w3.org/2000/svg" height="32" width="26" viewBox="0 0 116.76708 149.28999" style="margin-top: 8px;fill: red">
  <defs id="defs3749" />
  <g transform="translate(-129.2905,-77.659)" id="g3744">
  <g id="g3728">
  <path style="stroke:#000000;stroke-miterlimit:10" id="path3726" d="m 243.873,168.241 c -9.344,35.617 -43.232,58.208 -56.131,58.208 -13.155,0 -45.451,-28.357 -56.387,-63.353 -7.225,-23.113 12.081,-42.038 26.423,-42.112 21.474,-0.11 23.813,12.248 32.681,9.449 9.898,-3.125 8.874,-12.884 28.954,-12.606 12.542,0.171 31.997,21.688 24.46,50.414 z" stroke-miterlimit="10" />
  </g>
  <path style="fill:#ffffff" id="path3730" d="m 156.34,148.723 c 0,5.688 -1.251,10.304 -2.801,10.304 -1.55,0 -2.801,-4.615 -2.801,-10.304 0,-5.689 1.251,-10.3 2.801,-10.3 1.55,0 2.801,4.611 2.801,10.3 z" />
  <path style="fill:#ffffff" id="path3732" d="m 170.818,175.338 c 0,5.688 -1.251,10.296 -2.802,10.296 -1.55,0 -2.802,-4.607 -2.802,-10.296 0,-5.689 1.252,-10.304 2.802,-10.304 1.55,0 2.802,4.615 2.802,10.304 z" />
  <path style="fill:#ffffff" id="path3734" d="m 190.6,203.347 c 0,5.695 -1.251,10.304 -2.801,10.304 -1.55,0 -2.802,-4.608 -2.802,-10.304 0,-5.688 1.251,-10.296 2.802,-10.296 1.549,0 2.801,4.607 2.801,10.296 z" />
  <path style="fill:#ffffff" id="path3736" d="m 211.903,175.338 c 0,5.688 -1.251,10.296 -2.801,10.296 -1.55,0 -2.801,-4.607 -2.801,-10.296 0,-5.689 1.251,-10.304 2.801,-10.304 1.55,0 2.801,4.615 2.801,10.304 z" />
  <path style="fill:#ffffff" id="path3738" d="m 192.462,160.498 c 0,5.688 -1.251,10.303 -2.794,10.303 -1.55,0 -2.802,-4.614 -2.802,-10.303 0,-5.692 1.251,-10.3 2.802,-10.3 1.543,0 2.794,4.608 2.794,10.3 z" />
  <path style="fill:#ffffff" id="path3740" d="m 226.324,148.723 c 0,5.688 -1.252,10.304 -2.802,10.304 -1.55,0 -2.802,-4.615 -2.802,-10.304 0,-5.689 1.251,-10.3 2.802,-10.3 1.55,0 2.802,4.611 2.802,10.3 z" />
  <path id="path3742" d="m 153.539,77.659 24.503,24.531 c 0,0 4.202,2.784 6.954,0.68 4.366,-3.328 32.268,-25.211 32.268,-25.211 l -12.842,21.712 c 0,0 -3.015,4.604 1.878,5.22 4.892,0.618 27.148,-6.854 32.126,19.917 0,0 -2.297,-15.409 -29.325,-11.513 0,0 -9.421,1.721 -14.719,6.841 0,0 -4.714,6.891 -12.138,1.244 0,0 -27.077,-15.366 -46.219,5.646 0,0 13.304,-25.724 34.792,-22.136 z" />
  </g>
</svg>"$)

Here too, some CSS has to be used + setting the width & height too.

A typical svg tag looks like this:

<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 1024 1024" style="margin-top: 14px;fill: black">...</svg>

Such svg tags are e.g. created when exporting it from Illustrator.

3. Images
These type of icons are just plain png/jpg images. Make sure you use the appropriate size of the image to load. e.g. if your icon is going to be 32×32, do not load a 1024×1024 image!

page.AddImageIcon("abm-batman", "../images/batman.png", 32, 32, "margin-top: 8px", "Batman")

In this case, width, height and the CSS are part of the load method.

You can find 65000+ of these icons here: https://icons8.com/

USAGE
Now you can use your loaded icons like you would load font based icons:
e.g.

Dim lblTest As ABMLabel
lblTest.Initialize(page, "lblTest", "This is some test {IC:#000000}abm-si-maki-basketball-11{/IC} with {IC:#000000}abm-aardbei{/IC} special icons {IC:#000000}abm-gmail{/IC} or {IC:#000000}abm-gmail2{/IC}.", ABM.SIZE_H6, True, "")
page.Cell(9,1).AddComponent(lblTest)

Note that the color in the {IC} tag must be set for compatibility reasons, but is ignored.

Another example is in the sidebar menu:

page.NavigationBar.AddSideBarItem("MDL4", "Batmans layer", "abm-batman", "")
page.NavigationBar.AddSideBarDivider
page.NavigationBar.AddSideBarItem("MDL5", "From icons8.com", "abm-facebook", "")
page.NavigationBar.AddSideBarDivider

Or on the action button:

Dim actionB As ABMActionButton
actionB.Initialize(page, "ActionB", "abm-aardbei-action", "", "")
page.AddActionButton(actionB)<span 				data-mce-type="bookmark" 				id="mce_SELREST_start" 				data-mce-style="overflow:hidden;line-height:0" 				style="overflow:hidden;line-height:0" 			></span>

Alwaysbusy

Click here to Donation and support ABMaterial