IMPORTANT! Do NOT use v6.57 anymore if you are using it to make ABMBANanoLibraries!
1. [FIX] ABMBANano Libraries losing Websocket connection ——————————————————– I had to find this out myself the hard way, but there was a major flaw in the generation of the ‘glue’ code: It only worked for ONE instance of the page. So as soon as two or more users accessed the page, the previous ones broke the connection.
You will have to recompile your existing ABMBANanoLibraries with this new version and make the changes in the calling ABM app described below:
Consequences on this fix:
a. Step 3. (Call this method in ConnectPage() to set the WebSocket) has to be removed b. In each call to an ABMBANano library in ABM, you must pass the WebSocket (ws) in the method as the first parameter.
2. [NEW] TranspilerOptions.Author and .IDECmment ———————————————— Added to comply with the latest version of B4J
As some may know, ABMaterial (ABM) has some basic JavaScript functionality build-in called B4JS. It was the predecessor of BANano. However, because of the design of ABM, B4JS could never reach its full potential. But from that came the idea to write a full blown true B4J to JavaScript Transpiler and BANano was born: (Thinking in retrospect, B4JS in ABM may have been a poor choice of words, as BANano covers much more that term)
In my day job at OneTwo, we use ABM in most of our projects. But sometimes we just miss that extra BANano can do on the Browser side. For new projects, we mostly use a BANanoServer instead of an ABMServer, but we still have many ABM apps running and continue to write extra functionalities to those apps. What if we could use BANano generated code IN ABMaterial?
Hence this new idea came up: If BANano could generate .b4xlib libraries, writing the ‘glue’ between ABM and BANano, that would be a great asset to add features to ABM!
Check out point 7 for more info on how it is done!
Changes:
1. [NEW] BANano.SetTabNotification(number As String)
Adds a notification number to the browsers tab. e.g. ‘(2) My Website’
2. [NEW] BANano.DeepClone(Obj as Object)
Deep Clones an object (e.g. a map)
3. [NEW] BANano.CreateElement(Tag as String) As BANanoElement
Creates a BANanoElement, not attached to something
4. [NEW] Use you own favicon.ico
If a favicon.ico is found in the /Files folder, it is used instead of the default BANano one.
5. [NEW] Optimisations saved to file
As the log only shows the first x of optimizations, the full list is now saved in OPTIMISATIONS.txt
6. [FIX] Fixes in how the Service worker handles additional Javascript Files
In some rare cases, the Service worker did not included some needed Javascript files. This part of the engine has been rewritten.
7. [NEW] BANano.BuildAsB4XLibForABM(LibraryVersion As String)
New transpilation to create ABMBANano b4xlib libraries for ABMaterial.
8. [NEW] BANano.RaiseEventToABM(eventName As String, eventParamNames as List, eventParamValues as List, Description as String)
Method to raise an event to a ABM, maximum two params because B4J only supports this maximum. Use a Map is more are needed. The Description will be added to the generated .bas file for ABM as comment.
See 7 for usage.
9. [NEW] BANano.Spread(variable as Object) as Object
Adds the spread operator (three dots) before the variable. e.g. BANano.Spread(myVar)
Would it be possible to write a full blown TeamViewer in B4X in just 30 days? That was the question posed about a week ago on the B4X forum. Not a small thing to ask, because how would one get started with something huge as this?
TeamViewer has a lot to offer:
Remote control a PC/Mobile
File Transfer
Clipboard copy between host and client
Advanced Remote functionalities like doing a Reboot
Reduce resolution/remove wallpaper on slow connections
Address book
Multiple connections
…
Could this be done?
Saif Sameer and his team of B4XCode.com say they can. And they even upped the ante by saying they would do it in just 30 days!
They have released the source code of some excellent Android/Desktop apps written in B4X the past few weeks so I have full confidence if they say they can, they will.
This small team of gifted programmers and UX designers have been selling the source code of some eye catching programs made in B4X:
Some of the projects B4XCode.com is selling
They have taken on this challenge in sort of a ‘crowdfunding’ way: if you sign in for just $50, you will receive the full source code of the project! (EDIT: extended to 2021-02-1, normal price $350)
I already did and many others and so should you. Even if you do not plan to use it, the learning experience on how they did it is probably the best money you will spend this year! I’m very excited about this project and am following its progress like a hawk.
We are only one week in, and the first results they’ve shown are very promising indeed.
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.
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… 😉
The latest version of BANano introduces the BANanoServer.b4xlib library, which is an easy to use wrapper around jServer, the same B4J lib ABMaterial uses. One can now use BANano to make stand-alone Web sites, but also interfaces for e.g. IoT and Raspberry Pi (when using the BANanoServer lib: as this is a normal B4J app, it can use all B4J libs on the server side) and build complex Web Apps.
NOTE: BANano still works without the BANAnoServer Library as it did before in case you want to use another backend like PHP, but only having to program in one language has huge advantages and is what RAD is al about.
When you will look at the code of BANanoServer, ABM users will recognize the powerful Caching system, Root, Websocket support and HTTP/2 filters. In BANano, all that code is removed from your own projects view (but still accessible as it is an open source .b4xlib).
Some ‘easy-to-use’ methods to communicate between the browser and the server are added (to call methods from each other and to exchange files).
More info and download BANano for free here: BANano Download
But the biggest news this week must have been the BOMB Erel from Anywhere Software dropped: B4A, the tool to make native Android apps, is from now on free to use for everyone!
From his announcement a couple of weeks ago:
I’m very proud in the continuing progress of B4X tools. I’m also very proud to be part of this amazing developers community.
In the last couple of years, I have a growing feeling that B4X tools do not realize their potential. While there are all kinds of development tools, B4X offers a unique set of features: simple, powerful, mature, great community, RAD, native, cross platform and more.
B4X tools are an excellent choice for a wide range of real-world use cases.
So, the question is how to make B4X more popular? Obviously, it is not a simple nor a short-term task.
A clear growth barrier is the fact that unlike most development tools today, B4A and B4i are not free. This wasn’t the case 10 years ago.
The big announcement today is that B4A will become free in a few weeks. The framework – set of internal libraries, will be open sourced.
We will accept contributions for B4A like currently done with B4J.
We’ve also secured funds from a US investor who shares my vision of making B4X a popular development tool. These resources will allow us to further expand.
Two huge points were raised here: for one, the tool that started it all (B4A) to make native Android apps, is now 100% free (download it here)! It is clear that nowadays paying for a development platform is so 2009. Some development vendors may exist for 20+ years, but haven’t grown accordingly and seem to be stuck in overprotecting their (outdated?) technology. Clearly, Erel is not one of them and is thinking on how to expand his tools into the next decade.
But the second part may be even more exciting: a private US investor has shown interest in the potential of B4X and shares Erel view of the future. This opens a whole new world to the development tool and allows to think a lot bigger. He can expand his team not only with new developers, but even more important, surround himself with professionals to market his brand and let the world know of the big impact his technology can have on any software developer.
Just read up on the forum (100K+ members) and one quickly realizes the brought spectrum of apps that can be written with B4X. It is used by big corporations like NASA, IBM and Bosch to name a few, but also by small companies and citizen developers. I for one use it daily in my day job at OneTwo and we feel so secure with the tools we use it as the foundation of all our software. Very rarely one comes across a tool where the developers are so close to its users and actually listens to their concerns and wishes. I remember having found a bug (yeah, you really have to go deep to find one) and after reporting it to the Anywhere Software team, 10 minutes(!) later, an update was released. This has never been seen and blocked me zero time in my own development.
I am a big fan of the B4X suite. It allowed me to write ABMaterial and BANano, two libraries to make modern Websites and Webapps. Something I can’t see me do in any other tool, at least not with such ease and speed. And it is fun, really fun to work with too. 🙂
So I think Yoda would concur: ‘The future, bright it is!’
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.
Some young and interested African tech enthusiasts completed their first IoT course using the free B4X tools from Anywhere Software, congratulations!
In a hackathon event in celebration with International Arduino Day 2019, a deep overview as seminar on B4X products and specifically utilizing B4R with famous micro controllers and technologies was organized.
1. GetViewFromLayoutArray() and GetAllViewsFromLayoutArray() has now an extra first parameter to allow adding the B4J class where the layout was loaded.
2. New params to config the paths for a build:
SCRIPTS_FOLDER
ASSETS_FOLDER
STYLES_FOLDER
3. BANanoSkeletonCSS added some properties. You will need to copy the new Library to your Additional Libraries folder if you have already used it. The CSS file was also changed.
4. New example Website with multiple pages. I made two versions, one using a Router, one without a Router.
IMPORTANT: they need to run on a real server (or e.g. the Chrome Server plugin!)
It is a real world example I wrote for a friend of mine who owns a B&B.
Demonstrates:
multi-pages
multilingual supports
browsers Back/Forward buttons support.
floating navigation bar
It is ridiculous how little programming I had to do to make his site. Finished the whole thing in a morning. 🙂
Both examples are heavily commented.
5. The following JavaScript DOM objects have been wrapped:
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.
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
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. 🙂
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:
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.
Cloudinary has a great free plan which can cover a great deal of the webapps you make without any additional cost:
Total images and videos: 300,000
Managed storage: 10 GB
Monthly net viewing bandwidth: 20 GB
In ABM, it can be used for two things:
1. Automatic upload/update of the generated CSS/JS files (the ones you find next to each index.html).
2. Your own assets, like images, videos or outer ‘RAW’ files (xls, doc, …)
Creating a free account on Cloudinary is pretty straight forward. You make a login and give your ‘cloud’ a name. You will then be able to get your API_KEY and API_SECRET from the management console: https://cloudinary.com/console:
1. Automatic upload/update when starting the .jar file.
Activate it in the main class:
' just some clear consts to explain the parameters
Dim UPLOAD_GENERATED_JSCSS As Boolean = True
Dim DO_NOT_UPLOAD_GENERATED_JSCSS As Boolean = False
Dim SHOW_DEBUG As Boolean = True
Dim DO_NOT_SHOW_DEBUG As Boolean = False
Dim CLEAR As Boolean = True
Dim DO_NOT_CLEAR As Boolean = False
#If RELEASE
ABM.ActivateCloudinary("DONATOR_KEY","CLOUD_NAME", "API_KEY", "API_SECRET",UPLOAD_GENERATED_JSCSS, "APPNAME", DO_NOT_SHOW_DEBUG)
#else
ABM.ActivateCloudinary("DONATOR_KEY","CLOUD_NAME", "API_KEY", "API_SECRET",DO_NOT_UPLOAD_GENERATED_JSCSS, "APPNAME", SHOW_DEBUG)
#end if
' this is for 2, your own assets
ABM.CloudinaryLoadImages(CLEAR)
ABM.CloudinaryLoadAudioVideos(DO_NOT_CLEAR)
ABM.CloudinaryLoadRawFiles(DO_NOT_CLEAR)
What will this code do?
It will create a folder structure in you Cloudinary cloud:
APPNAME/css
APPNAME/js
Every time you start your jar, both these folders will be emptied (so do not put anything else in it!) and the most recent generated .js/.css files will be uploaded. Note: depending on the size of your app, this can take quite some time. e.g. for the demo app it takes about 3 minutes.
There is nothing more you have to do, ABM will have changed all the calls in the HTML from your local drive to the CDN.
2. Your own Assests:
As you can see in (1), we pre-load all the urls of the images/videos and other files. That way it is pretty simple to use the e.g. if we want to use an image using the ABM.CloudinaryGet() command:
Dim img4 As ABMImage
img4.Initialize(page, "img4", ABM.CloudinaryGet("demo/images/sample-1"), 1)
page.Cell(6,1).AddComponent(img4)
As parameter it takes the ‘publicId’ of the asset. In my case my APPNAME was demo, I created a folder images in it in the Cloudinary console and uploaded a file called sample-1.jpg so the public id becomes:
demo/images/sample-1
Note: I’ve noticed when uploading images via the cloudinary console, the public id cuts of the file extension.
The result of the ABM.CloudinaryGet command will be something like:
It is very easy to upload and organize you assets through the Cloudinary console app:
but sometimes you will want to upload it through B4X code.
So the following API methods do exist:
' using these 3 methods will automatically add them the the internal Cloudinary map
' so you can use them with the ABM.CloudinaryGet() method without reloading.
' The next time you restart your .jar, they will also be loaded.
ABM.CloudinaryUploadImage("publicId", fullFilePath)
ABM.CloudinaryUploadVideo("publicId", fullFilePath)
ABM.CloudinaryUploadRawFile("publicId", fullFilePath)
ABM.CloudinaryDeleteImage("publicId")
ABM.CloudinaryDeleteVideo("publicId")
ABM.CloudinaryDeleteRawFile("publicId")
ABM.CloudinaryDeleteAllImagesWithPrefix("prefix")
ABM.CloudinaryDeleteAllVideosWithPrefix("prefix")
ABM.CloudinaryDeleteAllRawFilesWithPrefix("prefix")
For the last 3 methods, you use a ‘prefix’ to delete files in bulk:
e.g. if I want to delete all images from my images folder, I would do:
Cloudinary has a wide range of API methods (e.g. for image manipulation etc), and maybe I will expand the B4X API to support it too, but for the moment using it as a CDN only, it suffices.
Note that this is a Donator only feature. ABM 4.35 will be available in a couple of weeks.
This 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.
<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: " & 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" & 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: " & 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 & " "
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" & 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" & 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" & 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" & ToastID, "green", "Please check the Checkbox!", 3000, False)
HasError = True
End If
If HasError = False Then
ToastID = ToastID + 1
Page.B4JSShowToast("CheckboxInput" & 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" & ToastID, "green", "Switch is ON", 3000, False)
Else
Page.B4JSShowToast("SwitchToast" & 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" & ToastID, "green", "Checkbox ON", 3000, False)
Else
Page.B4JSShowToast("CheckboxToast" & 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" & ToastID, "green", "Active Radio = " & 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: " & start)
Log("B4JS Stop: " & 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;"></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!
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 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);
}
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
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.
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)
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.