[B4X] BANano new UI library

Image2

 

Great new BANanoLibrary by Kiffy: BANanoKendoUI

Unlike other RAD Web tools, with B4J and BANano, you are not limited to UI frameworks like bootstrap. Just use the one you like!

More info: https://www.b4x.com/android/forum/threads/banano-kendo-ui-core-wrapper.103257/

Alwaysbusy

or

Click here to Donation and support BANano & ABMaterial

Advertisements

[B4X] BANano 2.25 released

BANano2.25

CHANGES:

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:

  • BANanoWindow
  • BANanoHistory
  • BANanoLocation
  • BANanoGeoLocation/BANanoGeoPosition (https only)
  • BANanoConsole
  • BANanoNavigator
  • BANanoScreen

Some are used in the examples in (4)

6. BANanoObject has been extended with:

  • AddEventListener
  • ClientLeft/Top/Width/Height
  • OffsetLeft/Top/Width/Height
  • ScrollLeft/Top/Width/Height

7. Other transpiler fixes.

Download: https://www.b4x.com/android/forum/threads/banano-a-sneak-peek-into-a-progressive-web-app-library.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: ABMaterial and using a CDN

cdn

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

The Benefits of using a CDN:

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

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

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

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

In ABM, it can be used for two things:

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

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

cloudinary1

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

Activate it in the main class:

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

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

APPNAME/css
APPNAME/js

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

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

2. Your own Assests:

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

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

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

demo/images/sample-1

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

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

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

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

cloudinary2

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

So the following API methods do exist:

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

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

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

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

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

ABM.CloudinaryDeleteAllImagesWithPrefix("demo/images/")

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

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

This concludes the tutorial.

Alwaysbusy

Click here to Donation and support ABMaterial

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

b4jstut07

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

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

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

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

B4JSTut07image

Lets dive into the B4X code:

The B4JS class B4JSInputChecker:

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

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

End Sub

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

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

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

   ' let the Keydown do its thing
   Return False
End Sub

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

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

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

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

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

   ' consume the event
   Return True
End Sub

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

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

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

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

   Dim HasError As Boolean = False

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

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

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

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

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

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

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

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

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

And creating the components in ConnectPage():

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

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

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

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

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

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

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

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

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

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

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

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

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

In our B4JS class, this is our method definition:

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

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

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

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

Alwaysbusy

Click here to Donation and support ABMaterial