Xojo 2020r1.1 Update: Web 2.0 evaluation continued

A month after the first release of Web 2.0, Xojo has released a maintenance update. Unfortunately, the slow workings of a Web 2.0 App have not been addressed.

I understand it will not be a small feat to fix this one. As described in my previous post, the problem is in the core design of their framework (especially the event system) and I can not believe anybody at Xojo hasn’t been able to predict this would cause problems, especially when bigger Web apps with lots of dynamically created components will be build. All we have seen now is very small ones and even there the problem already shows.

For example here is a run of such a new small app named Showcase. Note that I have a 200Mbps broadband internet connection and speed has never been an issue.

Xojo is giving as the main reasons for such slow behavior the distance from the server, latency, number of hops and the quality of your network connection. Well, of course that may be a variable in the equation! But, this is something EVERY web app builder has to take into account. You can not assume your users are living next door to your server. That is why the golden rule is: avoid many roundtrips to the server at all costs!

But here is the rub: this is exactly what Xojo’s event system does by design. As demonstrated in my previous post, adding 100 components does 200+ roundtrips to the server, it even happens for events like shown and hidden.

I can’t see an immediate fix for this problem. Maybe HTTP/2 could help a bit, or WebSockets, or doing stuff in batch making less requests? Or maybe there just isn’t one quick fix and going back to the whiteboard and rethink the whole event system is the only right decision in the long run. Anyhow, action will be needed from Xojo if they want Web 2.0 to succeed. There is little point in evaluating the other things in Web 2.0 without this being addressed first.

Alwaysbusy

Xojo 2020R1 Web 2.0 first look

Last week, Xojo Inc. released their first version of Web 2.0, a framework to create WebApps to replace their old one. An overhaul was long overdue and as I recall, a first peek was given at XDC2017. So given the development time span, they must have come up something extra ordinary. I was eager to give it a try!

I fired up de tool and picked a Web Example: WebGrid Container. First thing one sees is some kind of mock-up browser. I guess I was actually expecting to see the real deal, but no biggie. On the right are the controls one knows from the desktop framework. There aren’t that to many to pick from and I couldn’t help but thinking they had to split up controls just to fill the library. For example there are three buttons (OK, Cancel, Standard), which are exactly the same HTML component, except some very basic styling. Same goes for the inputs (normal, password, email, number, phone and url) which are in HTML just one input tag, with a different type attribute. This could’ve easily just been a property of one input component. But alright, it’s eye candy 😉

I was especially interested in how they tackled the 12-columns system in a WYSIWYG designer. This is the most widely used system in nowadays responsive web design and as Bootstrap (the CSS framework that is behind Xojo Web) was one of the pioneers of this system, they must have come up with something special, no? Well, for the love of God, I could not find it! After some digging in the forum, it looks like they picked flex and it is currently only available on a WebContainer. Really feels like a missed opportunity to come up with something truly innovative.

Time to hit the run button! Because you always have to compile a Xojo app every time you want to test the result, this can take take quite some time if you are working on a big project. Xojo has no live code swapping so this is something you have to do even for a minor change, like changing the color of a label. But that is just the way the tool works.

Your default browser fires up and the website is shown, as designed in the editor. Simple and you can immediately start testing your Webapp! But something felt not right. This is a very basic WebApp but it didn’t feel like it was loading very swiftly. Not exuberantly slow but just off and well noticeable e.g. when you are on 3G for such a small app. Time to use the Chrome Dev Tools and look what is going on here…

The Network tab immediately revealed this WebApp loaded A LOT of CSS and JavaScript files! Some of them had nothing to do with the app I was running (charts?, summernote (their web editor)?, datepicker?, …). In fact, Chrome Coverage showed about 9 CSS/JavaScript files were never used. Or there is a bug, or they don’t look how they can optimize the WebApp at all. This ‘little’ app loads 1.7MB of resources and does 44 requests to the server, just to load.

Still, somewhat forgivable, as nowadays we all have fiber internet and caching will help on future reloads. And I can’t imagine there isn’t a future plan from Xojo to put in some serious time to optimize this because they really should: running some quick speed tests show that the numbers are really bad (on Xojo’s ‘Eddie’s Electronics Demo’).

Google PageSpeed Insights (but similar results with tools like GTMatrix)

I know, I know… Some will argument that this is for Websites not WebApps. But is there really a difference these days? And why do my (very big) WebApps get scores of 98+? I take real pride in this and it is really fun if you can show this to your client:

Not everyone is so obsessed with speed like me and looking to squeeze out every millisecond possible, but a RAD tool like Xojo should definitely not neglect this, especially since they have full control over the end result. Luckily, there is plenty of things they can do to get these numbers up and give the user a much better experience firing up a Xojo WebApp.

But then I actually used the app. I’ve put in ‘100’ and hit Set. This is a real time recording:

Here there must be something fundamental very wrong on how this works!

From here on I can only make an educated guess on the internal workings of Xojo Web 2.0 but this doesn’t seem right. It looks like every control is added individually instead of in batch, doing way to many requests to the server (my ‘100’ added 200+ requests). One can see on the right a lot of xhr calls (which looks like some workaround Xojo uses as they do not support bidirectional WebSockets) and the browsers somehow ‘polls’ for events, saved as a file on the server. I can’t explain the delay before the adding of the controls starts (about 12 seconds). It just feels like the app ‘hangs’. I consider a response from a user’s action under 300ms as reasonable.

However it is done, it doesn’t work. No way I could present such a demo to a client and still hope to get the job. A WebApp is expected to be swift and behave just like a native app. I have written WebApps that load heavy lists, animate pages, require quick MySQL access through REST APIs, etc… and are just as fast as their native counterpart so I know it can be done. It always surprises me how freaking fast JavaScript and a browser actually is! IF you do it right…

Conclusion

I really hope for Xojo Inc. this is some kind of public ‘pre-alpha’ release and by the end of the year I can re-test this with better results. A quick look at the forum shows other basic bugs that could’ve been prevented with proper testing and Web 2.0 is missing some deal breaking features and events. But more troubling for me is the missed opportunity to design truly responsive WebApps using the 12-column system and the speed issues it has.

Given the time they spend on it, and that it is not cheap ($699), my overall feeling is a bit of a disappointment. Sure, for someone who does not program as their day to day job, it is a nice-to-have. If you want to impress your local bridge club with the WebApp you wrote last week to keep scores, that will work. But for prime-time-real-world-production WebApps, no, Xojo’s Web 2.0 is far from ready.

Still, looking into their JavaScript code, someone within Xojo Inc. must really, really, really love XojoWeb so there is some hope development won’t stop like it did with iOS… 😉

Until next time and happy programming!

Alwaysbusy

[B4X] BANano 3.09 Released

CHANGES:

1. NEW: BANano .b4xlib libraries

The big change is the introduction of being able to make and use BANano libraries in the B4X .b4xlib format.
This has a couple of profound advantages over normal .jar libraries for BANano:

a. Because the libraries are compiled at the same time as the app, BANano can remove classes that are not used in the app. This works best for libraries that use Custom Views.

E.g. suppose you made a big library with a lot of components like DatePickers, Grids, etc. If you don’t use a certain component, then that code is not included in your final app. This can reduce the size of the generated app considerably.

I think the way BANano generates a build (very modular) is now paying off big time.
But, this really depends on how modular the library builder has written his library.

e.g. if a class is used in a method in a module, but this method is never called in your app, all the classes (and the DependsOnAsset, see 6) will still be included.

b. As all the assets are included in the .b4xlib, you don’t have to worry about adding them in your app anymore: they are copied and included automatically. This means less clutter in your Files tab and no more .AddCSSFile or .AddJavascriptFile that are just needed for a library. You just have to worry about the assets you use in your app.

c. No more need to make a library in two steps (BuildAsLibrary + Compile to Library)

As a Library Builder, there is nothing special you have to do in B4X, except using:

BANano.BuildAsB4Xlib("1.01")

I’ve tried it on all the libs and projects I have and it looks like it works, but maybe there are still some problems in it. I had to make a lot of changes to the core of BANano to make this possible, so the chance exists.

In short: TAKE A BACKUP! :)

2. NEW: The generated .js files are in Build (release) modus a lot smaller (tests showed sometimes 50% smaller)

3. CHANGE: UseServiceWorker is now by default false. You will have to set it to True if you want to use a Service worker.

4. NEW: MinifyOnline now also minifies CSS files

5. NEW: MergeAllCSSFiles and MergeAllJavascript files: will create one CSS and one JS file by mergin them.

6. NEW: DependsOnAsset

When creating e.g. a Custom View, you can set if it Depends on certain CSS, Javascript or other asset files.
For CSS and Javascript, you must still use the AddCSSFile & AddJavascriptFile in AppStart.

7. Because the Transpiler gets more and more options, I created a new object BANano.TranspilerOptions that hosts those options.

The old ones are marked DEPRECIATED (they still work, but will eventually be removed). A warning is shown.

8. NEW options: ShowWarningsDeadCode & RemoveDeadCode

Only works in Build

Shows a warning in the log if the transpiler suspects some code is dead (never used).
This is handy, especially in the final stage of development to remove code (or comment out) that is never used.

Methods with a _ in their name are always considerd to be needed.

You can then use the RemoveDeadCode property to prevent GENERATING dead javascript code.

9. Added the Fetch API: see here

Download here:

Alwaysbusy

or

Click here to Donation and support BANano & ABMaterial

[B4X] BANano 2.19 released


CHANGES:

1. Fix for bug in release mode for #if CSS commenting out all generated Javascript after it.

2. Support for #ExcludeFromLibrary. Modules having this tag set to true will not be compiled into the BANano library.

3. Fix for bug ‘OR’ between two String functions:

e.g. previously failed:

Dim myString As String = "Foo"
If myString.ToLowerCase.Contains("foo") Or myString.ToLowerCase.Contains("bar") Then
   Log("!")
End If

4. Fix for List AddAll(), AddAllTo()

5. BANanoObject.Initialize2() second param can now also be an Array. This makes it possible to do this:

In Javascript:

var myGraph = new Dygraph(div, data);

BANano:

DygraphObject.Initialize2("Dygraph", Array(div, data))

6. Support for BANanoPromise. See this tutorial: https://www.b4x.com/android/forum/threads/banano-working-with-promises.102413/

7. 3 shortcut methods on the BANanoElement:

.GetField
.SetField
.RunMethod

All three are BANanoObject methods, now also available on BANanoElement as shortcuts.

So you can now do:

Dim UploadedFiles() As String = BANano.GetElement("#fu").GetField("files").Result

instead of having to do (both still work):

Dim UploadedFiles() As String = BANano.GetElement("#fu").ToObject.GetField("files").Result

I hope this will help confused users who do not always understand when to switch from BANanoElement to BANanoObject.

Download:

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

or

Click here to Donation and support BANano & ABMaterial

 

[B4X] BANano 2.15 released


CHANGES:

1. Support for B4J Type e.g. Type Person(Name As String, Properties As Map, Scores(10) As Int)

2. Support for NumberFormat and NumberFormat2

3. BANano.Sleep() is now just B4Js normal Sleep(). BANano.Sleep() will be depreciated in the future.

4. BANano.GetType() and BANano.TypeOf are now just B4Js normal GetType(). Both BAnano functions will be depreciated in the future.

5. BANanoSQL new method .ExecuteCallBack(). See for more info: https://www.b4x.com/android/forum/threads/banano-extending-event-driven-bananosql-with-own-callbacks.101920/#post-639714

6. BANanoSkeleton added SKRange, SKRadio, SKSwitch and SKNaviagationBar.
All views now have a Visibility property.

IMPORTANT:
– BANanoSkeleton_Files.zip contains new files. If you use it already in a project, you must copy the new files in your projects Files folder.
– Open all your layouts and save them so the new property Visible will be saved in the layout.

7. Bug fixes in the Transpiler

Download: https://www.b4x.com/android/forum/threads/banano-website-app-wpa-library-with-abstract-designer-support.99740/#post-627764

Alwaysbusy

or

Click here to Donation and support BANano & ABMaterial

 

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

DOWNLOAD THE LATEST VERSION OF BANANO:

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

Check out part 1 of this tutorial first! https://alwaysbusycorner.com/2019/01/20/b4x-banano-v2-b4j-abstract-designer-part-1/

INTRODUCTION

Now that we have our custom view created, let’s use it. As said in part 1, there are a couple of ways which I will go in to deeper now.

But first, I give you the steps on how to create a library out of part 1. This will allow us to use Sub + TAB in the B4J IDE.

CREATING A LIBRARY

This is a two step process. Make sure you have the following lines in your library AppStart code:

' start the build
#if release
       BANano.BuildAsLibrary()
#else
       BANano.Build(File.DirApp)
#end if

Now for step 1, run your library in release mode. The .js, .dependson (.php if you use it) and _Files.zip (if you have assets in the Files tab) will be created in your Additional Libraries folder. It will transpile everything in each .bas file. The Main class will be skipped.

Step 2 is just compiling the library with the B4J IDE ‘Compile To Library’

Give it the same name as you have used in the BANano.Initialize declaration. The .jar and .xml files will also be generated in your Additional Libraries folder.

So to summerize: you will now have the following files in your Additional Libraries folder:

yourlib.js
yourlib.dependsOn
yourlib.jar
yourlib.xml
yourlib.php (if you have used inline php in your library)
yourlib_Files.zip (if you had assets in your Files tab)

1. USING THE CUSTOM VIEW IN A LAYOUT
Make a new B4J UI project, and the BAnano.jar and yourlib.jar in the libraries. Also, unzip the yourlib_Files.zip file to your projects Files folder (watch out that you do not make a subfolder!).
Again let’s start with adding the default BANano project main 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
   BANano.Build(File.DirApp)
 
   ExitApplication
End Sub

' HERE STARTS YOUR APP
Sub BANano_Ready()

End Sub

Save and open the Abstract Designer. In the Views Menu, under custom views you will find your newly created custom views:


Before we start using our views, a quick note: as currently B4J Custom views can only set their parent to main, make sure there is some space between each view to allow BANanos own algorithm to determine who is the parent. The absolute position of a view in the Designer has no relevance in BANano anyway.

Example:

So, now we add our Custom views. You will notice the Custom properties in the Properties Pane. Only the following properties can be used (the rest is ignored):

Once you are satisfied with your design, save it and generate the events you want to use:

IMPORTANT: If you plan to load this layout only ONCE at the time with BANano.LoadLayout(), you can select the view itself (e.g. Txt1 in this example) to generate the Private Txt1 As SKTextBox line. If you plan to use BANano.LoadLayoutArray(), then you can NOT use this.

This makes sense, as a single layout matches up with one single view. In case of an array of such layouts, the is no One-to-One relation. But we go into this deeper in just a second.

The code will be generated for you:

Sub Process_Globals
   Private BANano As BANano
   ...
   Private Txt1 As SKTextBox
End Sub

...

Sub Txt1_Focus (event As BANanoEvent)

End Sub

Sub Txt1_Blur (event As BANanoEvent)

End Sub

Sub Txt1_Keydown (event As BANanoEvent)

End Sub

Sub Txt1_KeyUp (event As BANanoEvent)

End Sub

Sub Txt1_Change (event As BANanoEvent)

End Sub

Sub Button1_Click (event As BANanoEvent)

End Sub

Sub SKTable1_Click (event As BANanoEvent)
 
End Sub

All you have to do now is load your newly created layout:

' HERE STARTS YOUR APP
Sub BANano_Ready()
   ...
   BANano.LoadLayout("#body", "layout1")
   ...
End Sub

This is just normal B4J stuff! :) Now you can code as you are used to.

2. ADDING CUSTOM VIEWS BY CODE
This is equally similar to normal B4J behaviour. First add a declaration of a View in globals:

Private btn As SKButton

Now initialize and add the button:

' create a dynamic button, not located in the Layout
btn.Initialize(Me, "Button2", "Button2")
btn.Text = "Dynamic Button"
btn.AddToParent("R2")

IMPORTANT NOTE: The first param in Initialize() MUST be Me. Only the class where the View is added to will be able to handle the events.



3. LOADING A LAYOUT MULTIPLE TIMES
Sometimes, you are going to want to re-use a certain layout multiple times. This can for example be because you made a layout for a list item, and now want to re-use it for each item in your list.
This can be done using the BANano.LoadLayoutArray method:

' loading layouts as array (multiple times loading the same layout)
For i = 0 To 4
       Dim Ret As Long
       Dim AllViews As Map
 
       Ret = BANano.LoadLayoutArray("#r3", "MultiLayout", (i=0)) ' only clear the parent if it is the first layout that is loaded
 
       ' ret returns a unique number you can use to get all views
       AllViews = BANano.GetAllViewsFromLayoutArray("MultiLayout", Ret)
 
       Dim mLabel As SKLabel = AllViews.Get("multilabel") ' always lowercase
       mLabel.Text = "I'm {C:#FF0000}{U}row " & (i+1) & "{/U}{/C} of a multi layout!"
 
       Dim mButton As SKButton = AllViews.Get("multibutton") ' always lowercase
       mButton.Text = "Multi Button " & Ret
Next

Nothing difficult here. The final parameter in LoadLayoutArray() can be used to clear the parent on which you are loading the layout (in this case #r3).
The method does return a ‘unique’ number. This is very useful to get all the views from your layout. We do this with the BANano.GetAllViewsFromLayoutArray() method.
The GetAllViewsFromLayoutArray() method returns a map with all the Views in it, for that layout, with instance ‘unique number’.
So you can just grab Views and start manipulating them.

NOTE: you may think you should ‘buffer’ this AllViews in a map yourself but this is not needed! In the generated Javascript, this will already be done for you so you would do it twice.

There is also a ‘helper’ method BANano.GetSuffixFromID() to know what this ‘unique’ number is, in case for example you want to make changes further in your code in a certain event.

Sub MultiButton_Click (event As BANanoEvent)
   Log(event.ID)
   Dim Ret As Long = BANano.GetSuffixFromID(event.ID)
 
   Dim Allviews As Map = BANano.GetAllViewsFromLayoutArray("MultiLayout", Ret)
   If Allviews <> Null Then
       Dim mButton As SKButton = Allviews.Get("multibutton") ' always lowercase
       mButton.Text = "Multi Button " & Ret & " changed!"
   End If
End Sub

This concludes the 2 part tutorial of the new UI system in BAnano v2. Possibilities are endless and so much closer to standard B4J than the UI system v1.
Alwaysbusy

or

Click here to Donation and support BANano & ABMaterial


		

[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

[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: 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: 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

B4X: ABMaterial 3.75 Public/4.00 Donators released

ABMDragonfly4.00

ABMaterial Dragonfly (4.00) for B4X is available. Yes, it has a new name! What started as version 3.81 got upgraded to a new major version, especially since it uses a new cache control system.

ABMaterial has always been one of the fastest RADs from its start, now over 2 years ago.  But with 4.00, we’ll take it to a whole new level!

Mindful and I have spend 5 days (and nights) checking out how we could cache ABMaterial to the extreme without losing any of its functionality.  The results are just ridiculous…

Finish times are less than 10% of the time on the second and next loads. So on 3G, on the users next visit(s), the WebApp is almost just as fast as on high speed cable!

And this system not only works with the same page. Once one page has been loaded, all the other pages can take gain of this system. Even when the user revisits your app much later.

Furthermore there is the new debug feature to check how your apps work on different device sizes.  See it at work here:

Add this snippet to you main module, AFTER starting the server:

#If DEBUG
' in debug mode, start the browser and open the app on all devices (DOES NOT WORK IF EDGE IS YOUR DEFAULT BROWSER!)
ABM.ViewerOpenAllDevices("http://localhost:" & port & "/" & ABMShared.AppName & "/", 100)

' or open a specific device as default (DOES NOT WORK IF EDGE IS YOUR DEFAULT BROWSER!)
'ABM.ViewerOpenDevice("http://localhost:" & port & "/" & ABMShared.AppName & "/", 300, ABM.VIEWER_TABLET)

' or just open de browser, no multiple devices (should work in Edge)
'ABM.ViewerOpen("http://localhost:" & port & "/" & ABMShared.AppName & "/")
#End If

Lots of new theme properties! Check out the demo on how to use  them in the new Dragonfly theme.  As winter is coming and days are getting shorter (at least here in the northern hemisphere), I went for a ‘Night’ theme this time.

ABMaterial DragonFly is now available on 750+ locations worldwide on CDN!  If you use the CDN, you do not need to upload the css/js/fonts folders to your own server anymore!

This means only your own assets (images etc) from the www folder have to be available on your server. De demo running on abmaterial.com for example is working like this.

Usage:

ABM.ActivateUseCDN("DONATORKEY", "https://cdn.jsdelivr.net/gh/RealAlwaysbusy/ABMaterial@v4.03/")

The over 20 new fixes and fulfilled wishes make ABMaterial 4.00 very stable, just like its ‘parent’ programming language: B4X.  It is a real joy knowing one can depend on a strong, bug free environment.  Erel from Anywhere Software rulez!

Happy programming!

Alwaysbusy

Click here to Donation and support ABMaterial

B4J: Creating a Star Rating component in ABMaterial

StarRating

Michał asked me in the feedback app to implement some kind of Star Rating component. As this is not so commonly needed, I thought this could be another good example of an ABMCustomComponent. One can ‘hover’ over the stars to set its value or it can be set and retrieved by code.

I show you how to do it in ABMaterial 3.75 first with the new features (released soon), and will then explain what needs to be changed for previous versions.

So let’s get started!

1. We create a new standard class CompStarRating:

'Class module
Sub Class_Globals
Public ABMComp As ABMCustomComponent
Dim myInternalName As String
End Sub

'Initializes the object. You can add parameters to this method if needed.
Public Sub Initialize(InternalPage As ABMPage, ID As String, radioName As String)
myInternalName = radioName
Dim CSS As String = $"
#half-stars-example .rating-group {
display: inline-flex;
}
#half-stars-example .rating__icon {
pointer-events: none;
}
/* This is an important part: we MUST override the Materialize CSS implementation of the the radio button! */
#half-stars-example .rating__input + label:before,
#half-stars-example .rating__input + label:after {
position: absolute !important;
left: -9999px !important;
}
#half-stars-example .rating__label {
cursor: pointer;
padding: 0 0.1em;
font-size: 2rem;
margin-top: 1rem;
}
#half-stars-example .rating__label--half {
padding-right: 0;
margin-right: -0.6em;
z-index: 2;
}
#half-stars-example .rating__icon--star {
color: orange;
}
#half-stars-example .rating__icon--none {
color: #eee;
}
#half-stars-example .rating__input--none:checked + .rating__label .rating__icon--none {
color: red;
}
#half-stars-example .rating__input:checked ~ .rating__label .rating__icon--star {
color: #ddd;
}
#half-stars-example .rating-group:hover .rating__label .rating__icon--star,
#half-stars-example .rating-group:hover .rating__label--half .rating__icon--star {
color: orange;
}
#half-stars-example .rating__input:hover ~ .rating__label .rating__icon--star,
#half-stars-example .rating__input:hover ~ .rating__label--half .rating__icon--star {
color: #ddd;
}
#half-stars-example .rating-group:hover .rating__input--none:not(:hover) + .rating__label .rating__icon--none {
color: #eee;
}
#half-stars-example .rating__input--none:hover + .rating__label .rating__icon--none {
color: red;
}"$
ABMComp.Initialize("ABMComp", Me, InternalPage, ID, CSS)
End Sub

Sub ABMComp_Build(InternalPage As ABMPage, internalID As String) As String
Return $"
<div id="half-stars-example">
<div id="${internalID}" class="rating-group"><input id="rating-0" class="rating__input rating__input--none" checked="checked" name="${myInternalName}" type="radio" value="0" />
<label class="rating__label" title="0 stars" for="rating-0"> </label>
<input id="rating-00" class="rating__input" type="radio" value="-1" />
<label class="rating__label rating__label--half" title="0.5 stars" for="rating-05"><i class="rating__icon rating__icon--star fa fa-star-half"></i></label>
<input id="rating-05" class="rating__input" name="${myInternalName}" type="radio" value="0.5" />
<label class="rating__label" title="1 star" for="rating-10"><i class="rating__icon rating__icon--star fa fa-star"></i></label>
<input id="rating-10" class="rating__input" name="${myInternalName}" type="radio" value="1" />
<label class="rating__label rating__label--half" title="1.5 stars" for="rating-15"><i class="rating__icon rating__icon--star fa fa-star-half"></i></label>
<input id="rating-15" class="rating__input" name="${myInternalName}" type="radio" value="1.5" />
<label class="rating__label" title="2 stars" for="rating-20"><i class="rating__icon rating__icon--star fa fa-star"></i></label>
<input id="rating-20" class="rating__input" name="${myInternalName}" type="radio" value="2" />
<label class="rating__label rating__label--half" title="2.5 stars" for="rating-25"><i class="rating__icon rating__icon--star fa fa-star-half"></i></label>
<input id="rating-25" class="rating__input" name="${myInternalName}" type="radio" value="2.5" />
<label class="rating__label" title="3 stars" for="rating-30"><i class="rating__icon rating__icon--star fa fa-star"></i></label>
<input id="rating-30" class="rating__input" name="${myInternalName}" type="radio" value="3" />
<label class="rating__label rating__label--half" title="3.5 stars" for="rating-35"><i class="rating__icon rating__icon--star fa fa-star-half"></i></label>
<input id="rating-35" class="rating__input" name="${myInternalName}" type="radio" value="3.5" />
<label class="rating__label" title="4 stars" for="rating-40"><i class="rating__icon rating__icon--star fa fa-star"></i></label>
<input id="rating-40" class="rating__input" name="${myInternalName}" type="radio" value="4" />
<label class="rating__label rating__label--half" title="4.5 stars" for="rating-45"><i class="rating__icon rating__icon--star fa fa-star-half"></i></label>
<input id="rating-45" class="rating__input" name="${myInternalName}" type="radio" value="4.5" />
<label class="rating__label" title="5 stars" for="rating-50"><i class="rating__icon rating__icon--star fa fa-star"></i></label>
<input id="rating-50" class="rating__input" name="${myInternalName}" type="radio" value="5" /></div>
</div>
"$
End Sub

public Sub GetCurrentRating(InternalPage As ABMPage) As Double
Dim script As String = $"return $('input[name=${myInternalName}]:checked').val();"$
Dim ret As Future = InternalPage.ws.EvalWithResult(script, Null)
InternalPage.ws.Flush

Return ret.Value
End Sub

public Sub SetCurrentRating(InternalPage As ABMPage, value As Double) {
Dim script As String = $" $('input[name=${myInternalName}][value="${value}"]').prop('checked', 'checked');"$
InternalPage.ws.Eval(script, Null)
InternalPage.ws.Flush
End Sub

' Is useful to run some initalisation script.
Sub ABMComp_FirstRun(InternalPage As ABMPage, internalID As String)
Dim script As String = $""$
InternalPage.ws.Eval(script, Array As Object(ABMComp.ID))
' flush not needed, it's done in the refresh method in the lib
End Sub
' runs when a refresh is called
Sub ABMComp_Refresh(InternalPage As ABMPage, internalID As String)
Dim script As String = $""$
InternalPage.ws.Eval(script, Array As Object(ABMComp.ID))
End Sub
' do the stuff needed when the object is removed
Sub ABMComp_CleanUp(InternalPage As ABMPage, internalID As String)
End Sub

ABMaterial users will notice I have added the CSS directly into the component. By doing so, we do not need to use the page.AddExtraCSSFile method in the Page_Build() method. (you can still do it this way, and in some cases it may be better as this part can be big. And in the final version, compress it!). In ABMaterial before version 3.75, using AddExtraCSSFile is the only way possible.

The only difficult part in the CSS was I had to override Materialize CSS’s implementation of a radio button (the ‘circle’). So we had to get rid of it.

2. Now we can make a variable for our StarComponent in the page Class_Globals, as we want to be able to Set and Get the value:

Dim myRating As CompStarRating

3. In Page_Connect() we add our component and a couple of buttons:

...
myRating.Initialize(page, "myRating", "rating")
page.Cell(1,1).AddComponent(myRating.ABMComp)

Dim btn As ABMButton
btn.InitializeFlat(page, "btn", "", "", "Get value", "")
page.Cell(2,1).AddComponent(btn)

Dim btn2 As ABMButton
btn2.InitializeFlat(page, "btn2", "", "", "Set 2.5 stars value", "")
page.Cell(3,1).AddComponent(btn2)
...
page.Refresh
...

4. And our Get and Set code in the buttons:

Sub btn_Clicked(Target As String)
Dim value As Double = myRating.GetCurrentRating(page)
Log(value)
End Sub

Sub btn2_Clicked(Target As String)
myRating.SetCurrentRating(page, 2.5)
End Sub

That is all! :)

Changes for versions of ABMaterial before 3.75

1. Save the CSS string into a text file (e.g. star.rating.css) and copy it to the /css/custom folder.
2. Remove the CSS param from ABMComp.Initialize(“ABMComp”, Me, InternalPage, ID, CSS)

ABMComp.Initialize("ABMComp", Me, InternalPage, ID) '<-- CSS param deleted

3. In Sub ABMComp_Build(InternalPage As ABMPage, internalID As String) As String remove the InteranlPage param:

Sub ABMComp_Build(internalID As String) As String

4. In Page_Build(), load the css file you created:

page.AddExtraCSSFile("custom/star.rating.css")

Alwaysbusy

Click here to Donation and support ABMaterial