B4X: Writing JavaScript with ABMaterials B4JS

B4JS

After a delay of more of a year working on other ABM stuff, I finally got back on track with B4JS. I will make a series of tutorials explaining how it works. B4JS is part of the ABMaterial library 4.25 which is going to be released next week.

INTRODUCTION

One way to look at B4JS is as some kind of new platform in B4X, that uses a very similar syntax as B4J. At runtime, the B4J source code is transpiled to pure JavaScript. The generated source code can then be used in e.g. an ABMaterial Web App, or maybe even as a base for another JavaScript library.

A typical B4JS class could look like this:

Sub Process_Globals
   Dim myString As String
   Public pubMap As Map

   Dim timer As Timer
   Dim myGlobalName As String = "GlobalAlain"
End Sub
'Initializes the object. You can NOT add parameters.
Public Sub InitializeB4JS()
   pubMap.Initialize

   Dim myName As String = "Alain"
   ' smartstrings do not support date, time or xml functions
   myString = $"Hello planet "B4X"! This is a test
    from ${myName} and ${myGlobalName}"$
   Log("myString.Contains('planet'): " & myString.Contains("planet"))
   Dim myLocalInt As Int = 15
   myLocalInt = myLocalInt * myString.Length

   LogMe("15 x the length of " & myString & " = " & myLocalInt)

   For i=0 To 50
       myLocalInt = myLocalInt + 2
       Select Case myLocalInt
           Case 20, 40
               Log(i)
               pubMap.Put("key" & i, i)
           Case Else
               Log("less than 20")
       End Select
   Next

   timer.Initialize("timer", 1000)
   timer.Enabled = True

   Dim sb As StringBuilder
   sb.Initialize
   sb.Append("lijn 1").Append(CRLF).Append("lijn 2")
   Log(sb.ToString)
   Log(sb.Length)
   sb.Insert(2,"X")
   Log(sb.ToString)
   sb.Remove(2,3)
   Log(sb.ToString)
End Sub
Private Sub Timer_Tick
   timer.Enabled = False
   Log("timer ticking")
   If timer.Enabled = False Then
       timer.Interval = timer.Interval + 1000
       Log("timer new interval: " & timer.Interval)
       timer.Enabled = True
   End If
End Sub

Looks very familiar, no? :)

Having the browser doing some stuff using JavaScript can have some big advantages by relieving some pressure from your server (checking if a form is filled in correctly, or changing the visibility of an ABMContainer if a user clicks a button).

But it also demands a great responsibility from the programmer not to expose to much information to the user. Never, ever write sensitive stuff like SQL, passwords etc in B4JS!

Another advantage is being able to expose some events (like the KeyUp/KeyDown events in an ABMInput field). They are deliberately omitted in ABM, because such events could potentially kill your server. But in Javascript, we could use them e.g. to check if the user could entry numbers, or if fits an email mask.

Having a Timer running on the browser side can also be handy.

First an overview of the syntax B4JS can handle:

OVERVIEW

Core library
Variable types:

Bit:

String:

List:

Map:

StringBuilder:

DateTime:

Keywords:

Operators:

Control Structures:

Timer:

Smart String Literal:

JSON library
JSONParser:

JSONGenerator:

ABMaterial library (as of 2018/03/10, for the latest list check the B4X forum):
ABMLabel:

EVENTS: B4JSOnClick, B4JSOnMouseEnter, B4JSOnMouseLeave

ABMInput:

ABMContainer:

ABMSwitch:

ABMCheckbox:

ABMRadioGroup:

ABMButton:

ABMPage:

ABMRow:

B4JSUniqueKey

EVENTS: B4JSOnClick, B4JSOnMouseEnter, B4JSOnMouseLeave

ABMCell:

B4JSUniqueKey

EVENTS: B4JSOnClick, B4JSOnMouseEnter, B4JSOnMouseLeave

TUTORIALS

In the next following days, I would like to cover the following topics:

01. Getting started
02. Core functions
03. Inline JavaScript
04. Running JavaScript on the server side (mini NodeJS?)
05. JSON & Ajax Calls
06. The UI (ABMaterial) connection

Alwaysbusy

Click here to Donation and support ABMaterial

Advertisement

B4X: Responsive containers in ABMaterial 4.25

ABMDragonfly4.00

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

Let’s watch it at work first:

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

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

gridContainer.IsResponsiveContainer = True

Done! 🙂

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

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

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

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

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

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

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

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

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

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

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

Params: left, right, top,bottom

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

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

Another year has passed:

2017 has been a great year for B4X and ABMaterial!

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

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

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

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

Alwaysbusy

Click here to Donation and support ABMaterial

B4X: ABMaterial 3.75 Public/4.00 Donators released

ABMDragonfly4.00

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

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

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

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

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

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

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

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

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

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

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

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

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

Usage:

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

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

Happy programming!

Alwaysbusy

Click here to Donation and support ABMaterial

B4J: Creating a Star Rating component in ABMaterial

StarRating

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

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

So let’s get started!

1. We create a new standard class CompStarRating:

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

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

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

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

Return ret.Value
End Sub

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

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

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

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

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

Dim myRating As CompStarRating

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

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

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

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

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

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

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

That is all! :)

Changes for versions of ABMaterial before 3.75

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

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

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

Sub ABMComp_Build(internalID As String) As String

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

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

Alwaysbusy

Click here to Donation and support ABMaterial

B4J: creating a reCAPTCHA component in ABMaterial

reCAPTCHA is a free service that protects your website from spam and abuse. reCAPTCHA uses an advanced risk analysis engine and adaptive CAPTCHAs to keep automated software from engaging in abusive activities on your site. It does this while letting your valid users pass through with ease.

First, we do have to register a Google API key for our WebApp here.

After registering, we receive our API key:
For this demo, I’m using the developer API key (which works with localhost) so it will show a warning that this is not the real key in red.
NOTE: this code is for ABMaterial 3.75. For earilier versions, see below what needs to be changed in the code.

Create a new class CompReCAPTCHA:

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

'Initializes the object. You can add parameters to this method if needed.
Public Sub Initialize(InternalPage As ABMPage, ID As String, APIKey As String)
   ABMComp.Initialize("ABMComp", Me, InternalPage, ID, "")
   mAPIKey = APIKey
End Sub
Sub ABMComp_Build(InternalPage As ABMPage, internalID As String) As String
   Return $"
<div id="${internalID}render" class="g-recaptcha" data-sitekey="${mAPIKey}"></div>
"$
End Sub
' Is useful to run some initalisation script.
Sub ABMComp_FirstRun(InternalPage As ABMPage, internalID As String)
   Dim script As String = $"grecaptcha.render(
"${internalID}render",
{"sitekey": "${mAPIKey}", "theme": "light"}
) "$
   InternalPage.ws.Eval(script, Array As Object(ABMComp.ID))
   ' flush not needed, it's done in the refresh method in the lib
End Sub

public Sub Reset(InternalPage As ABMPage)
   Dim script As String = $"grecaptcha.reset()"$
   InternalPage.ws.Eval(script, Null)
   InternalPage.ws.Flush
End Sub

public Sub CheckValidation(InternalPage As ABMPage) As Boolean
   Dim script As String = $"return (grecaptcha && grecaptcha.getResponse().length !== 0);"$
   Dim ret As Future = InternalPage.ws.EvalWithResult(script, Null)
   InternalPage.ws.Flush

   Return ret.Value
End Sub
' runs when a refresh is called
Sub ABMComp_Refresh(InternalPage As ABMPage, internalID As String)
   Dim script As String = $""$
   InternalPage.ws.Eval(script, Array As Object(ABMComp.ID))
End Sub
' do the stuff needed when the object is removed
Sub ABMComp_CleanUp(InternalPage As ABMPage, internalID As String)
End Sub

In BuildPage(), add the Google api:

page.AddExtraJavaScriptFile("<a class="linkification-ext" title="Linkification: https://www.google.com/recaptcha/api.js" href="https://www.google.com/recaptcha/api.js">https://www.google.com/recaptcha/api.js</a>")

In Class_Globals make a variable:

Dim myReCAPTCHA As CompReCAPTCHA

In ConnectPage create the component (and a couple of buttons):

...
' note that this is Googles demo API key.  Use your own!
myReCAPTCHA.Initialize(page, "myReCAPTCHA", "6LeIxAcTAAAAAJcZVRqyHh71UMIEGNQ_MXjiZKhI")
page.Cell(1,1).AddComponent(myReCAPTCHA.ABMComp)

Dim btn As ABMButton
btn.InitializeFlat(page, "btn", "", "", "Submit", "")
page.Cell(1,1).AddComponent(btn)

Dim btn2 As ABMButton
btn2.InitializeFlat(page, "btn2", "", "", "Reset", "")
page.Cell(1,1).AddComponent(btn2)

' refresh the page
page.Refresh
...

The code in the buttons to check its validation and to reset the captcha.

Sub btn_Clicked(Target As String)
   Dim bool As Boolean = myReCAPTCHA.CheckValidation(page)
   Log(bool)
End Sub

Sub btn2_Clicked(Target As String)
   myReCAPTCHA.Reset(page)
End Sub

Et voila, we do have a new reCAPTCHA component at our disposal in ABMaterial!

For ABMaterial pre 3.75, make these changes in the code:

Public Sub Initialize(InternalPage As ABMPage, ID As String, APIKey As String)
   ABMComp.Initialize("ABMComp", Me, InternalPage, ID) ' last param removed
   mAPIKey = APIKey
End Sub
...
Sub ABMComp_Build( internalID As String) As String ' InternalPage param removed
   Return $"
<div id="${internalID}render" class="g-recaptcha" data-sitekey="${mAPIKey}"></div>
"$
End Sub

Until next Time!

Alwaysbusy

Click here to Donation and support ABMaterial

B4J: Printing/Reporting with upcoming ABMaterial 3.75

2
A sneak peek on a new feature of ABMaterial for B4J I’m working on: Printing/Reporting.

This is an example of a report in ABMaterial. You build it right into your other pages and just by setting a couple of parameters, you control what will be shown on screen and what will be printed.

1
The block your see above ‘INVOICE’ will not be printed, just my setting lbl.IsPrintable = false.  What is shown on screen you control by using the normal ABM.VISIBLE_ constants. For example there are some additional notes with a chart that you can’t see here on screen, but will be printed.

You can use almost every ABM component in your report. Good examples are the ABMChart, the ABMChronologyList or even like in this example you can let your client sign the document using an ABMSignaturePad and print it right out.

The ABM components avoid being split over two pages (e.g. an image will be printed on the next page if it does not fit). Browsers let you control very little on how things are printed, but I’ve added some things like ReportFooter.row(3).PrintPageBreak = ABM.PRINT_PAGEBREAK_BEFORE_ALWAYS to give you some control.

Note that the navigation bar is not printed either. All of this is taken care for you automatically by ABMaterial!

3
Some example pdf prints from the demo app (without making any modification to the source code!). Try printing the same pages with ABMaterial 3.50 or before and you’ll see the difference.

http://gorgeousapps.com/PrintExamples.zip

Making the above ‘invoice report’ is really simple and in the spirit of ABMaterial. I’ve created a new component ABMReport (and ABMReportBlock) which you can use just like you would make an ABMCustomComponent.

Relevant source code snippet of the above report (the Build event is where the magic happens, the rest are more helper methods):

Sub Class_Globals
   Dim ABM As ABMaterial
   Dim Report As ABMReport

   ' some CSS to format the 'body' of the report
   Dim ReportCSS As String = $"
   .repheader, .repfooter, .repheaderright, .repfooterright {
     background-color: lightgray;
     font-size: 1.8rem;
   }
   .repheaderright, .repbodyright, .repfooterright {
     text-align: right;
   }
   .repbody, .repbodyright {
     font-size: 1.5rem;
   }

   @media only print {
     html { font-size: 60%}
   }"$

   Dim mSQL As SQL 'ignore
   Dim mInvoiceID As Int 'ignore
End Sub

Public Sub Initialize(InternalPage As ABMPage, ID As String)
   Report.Initialize("Report", Me, InternalPage, ID, ReportCSS, "")
End Sub

Sub Report_Build(InternalPage As ABMPage, internalID As String)
   ' the report header
   Dim ReportHeader As ABMContainer = Report.Header.InitializeAsContainer(InternalPage, "reportheader", "", ABM.PRINT_PAGEBREAK_INSIDE_AVOID)
   ReportHeader.AddRows(1,False, "border").AddCells12(1,"")
   ReportHeader.AddRows(1,False, "").AddCellsOS(1,0,0,0,9,9,9,"").AddCellsOSMP(1,0,0,0,3,3,3,20,0,0,0,"right")
   ReportHeader.AddRows(1,False, "").AddCellsOS(1,0,0,0,7,7,7,"").AddCellsOS(1,0,0,0,5,5,5,"")
   ReportHeader.BuildGrid

   ReportHeader.CellR(0,1).AddComponent(BuildLabel(InternalPage, "repTitle", "{B}I N V O I C E{/B}", ABM.SIZE_H4, "center", True, ABM.VISIBILITY_ALL))
   ReportHeader.CellR(1,1).AddComponent(BuildLabel(InternalPage, "repAddress", "Jonathan Neal{BR}{BR}101 E. Chapman Ave{BR}Orange, CA 92866{BR}{BR}(800) 555-1234",ABM.SIZE_H5, "", True, ABM.VISIBILITY_ALL))
   ReportHeader.CellR(0,2).AddComponent(BuildImage(InternalPage, "repImg", "../images/logo2.png", True, ABM.VISIBILITY_ALL))
   ReportHeader.CellR(1,1).AddComponent(BuildLabel(InternalPage, "repCompany", "{B}Some Company{/B}{BR}{B}c/o Some Guy{/B}", ABM.SIZE_H4, "", True, ABM.VISIBILITY_ALL))
   ReportHeader.CellR(0,2).AddComponent(Build3Block(InternalPage, "repInvoiceData", "{B}Invoice #{/B}", "101138", "{B}Date{/B}", "Januari 1, 2017","{B}Amount Due{/B}","$ 600.00", True, ABM.VISIBILITY_ALL)   )

   ' the report body
   Dim Body As ABMReport
   Body.Initialize("Body", Me, InternalPage, internalID & "body", "", ABM.PRINT_PAGEBREAK_INSIDE_AVOID)
   ' header of the body
   Body.Header.InitializeAsTexts(Array As String("Year(s) Overview", "Q1", "Q2", "Q3", "Q4"), Array As Int(40,15,15,15,15), Array As String("repheader","repheaderright","repheaderright","repheaderright","repheaderright"), "")

   ' detail lines: here you can e.g. run queries but for demo purposes let's do everything manual
   Dim sums(5) As Int
   For i = 1 To 9
     ' change the class of the last column on the second row
     Dim ColorClass As String = "repbodyright"
     If i = 2 Then
       ColorClass = "repbodyright red-text"
     End If

     ' create a text block
     Dim block As ABMReportBlock
     block.InitializeAsTexts(Array As String("Year 200" & i, 200*i, 300*i, 400*i, 500*i), Array As Int(40,15,15,15,15), Array As String("repbody","repbodyright",ColorClass, "repbodyright","repbodyright"), "")
     Body.AddBodyBlock(block)

     ' dirty way to make the sums
     For k = 1 To 4
       sums(k) = sums(k) + (20 + (k-1)*100) * i
     Next
   Next

   ' footer of the body
   Body.Footer.InitializeAsTexts(Array As String("{NBSP}", sums(1) , sums(2), "{B}" & sums(3) & "{/B}", sums(4)), Array As Int(40,15,15,15, 15), Array As String("repfooter", "repfooterright","repfooterright","repfooterright","repfooterright"), "")
   ' add the subreport 'body' to the body of the main report
   Report.AddBodySubReport(Body)

   ' the report footer
   Dim ReportFooter As ABMContainer = Report.Footer.InitializeAsContainer(InternalPage, "reportfooter", "", "")

   ReportFooter.AddRows(1,False, "").AddCells12(1,"")
   ReportFooter.AddRows(1,False, "").AddCellsOS(1,0,0,0,2,2,2,"").AddCellsOSMP(1,0,0,0,5,5,5,0,0,10,40,"").AddCellsOS(1,0,0,0,5,5,5,"")
   ReportFooter.AddRows(1,False, "").AddCells12(1,"")
   ReportFooter.AddRows(1,False, "").AddCells12(1,"")
   ReportFooter.AddRows(1,False, "").AddCellsOSMP(1,4,4,4,4,4,4,0,0,0,0,"")
   ReportFooter.AddRows(2,False, "").AddCells12(1,"")
   ReportFooter.BuildGrid

   ReportFooter.CellR(1,1).AddComponent(BuildLabel(InternalPage, "RepSignatureLbl", "Please sign this document", ABM.SIZE_H5, "", True, ABM.VISIBILITY_ALL))
   ReportFooter.CellR(0,2).AddComponent(BuildSignature(InternalPage, "repSignature", True, ABM.VISIBILITY_ALL))
   ReportFooter.CellR(0,3).AddComponent(Build3Block(InternalPage, "repTotalData", "{B}Total{/B}","$ 600.00","{B}Amount Paid{/B}","$ 0.00","{B}Balance Due{/B}","$ 600.00", True, ABM.VISIBILITY_ALL))

   ' avoid row 2 to be broken (can give weird results, depends on browser support!)
   ReportFooter.row(2).PrintPageBreak = ABM.PRINT_PAGEBREAK_INSIDE_AVOID

   ' alaways break before printing row 3
   ReportFooter.row(3).PrintPageBreak = ABM.PRINT_PAGEBREAK_BEFORE_ALWAYS

   ' all the rest is only visible on the print, not on the screen
   ReportFooter.CellR(1,1).AddComponent(BuildLabel(InternalPage, "AdditionalNotes", "{B}A D D I T I O N A L {NBSP}  N O T E S{/B}", ABM.SIZE_H4, "center", True, ABM.VISIBILITY_HIDE_ALL))
   ReportFooter.CellR(0,1).AddComponent(BuildDivider(InternalPage, "divider", True, ABM.VISIBILITY_HIDE_ALL))
   ReportFooter.CellR(0,1).AddComponent(BuildLabel(InternalPage, "note1", "Note 1: A finance charge of 1.5% will be made on unpaid balances after 30 days.", ABM.SIZE_H6, "", True, ABM.VISIBILITY_HIDE_ALL))
   ReportFooter.CellR(1,1).AddComponent(BuildLabel(InternalPage, "note2", "Note 2: Your current purchase chart.", ABM.SIZE_H6, "", True, ABM.VISIBILITY_HIDE_ALL))

   ' create a pie chart
   Dim chart5 As ABMChart
   chart5.Initialize(InternalPage, "chart5", ABM.CHART_TYPEPIE, ABM.CHART_RATIO_SQUARE, "chart5theme")
   chart5.Visibility = ABM.VISIBILITY_HIDE_ALL
   ' Something special, Label interpolation.  the value will transformed into a percentage format (This is Javascript!)
   Dim SumValues As Int = 30+50+70+80+100+140+170
   chart5.OptionsPie.LabelInterpolation = "Math.round(value / " & SumValues  & " * 100) + '%'"
   ' add ONE serie
   Dim Serie5A As ABMChartSerie
   Serie5A.InitializeForPie
   Serie5A.SetValues(Array As Int(30,50,70,80,100,140,170))
   chart5.AddSerie(Serie5A)
   ' add the chart to the cell
   ReportFooter.CellR(1,1).AddComponent(chart5)
End Sub

' Helper Methods
Sub BuildLabel(internalPage As ABMPage, ID As String, Text As String, Size As String, theme As String, isPrintable As Boolean, Visibility As String) As ABMLabel
   Dim tmpLbl As ABMLabel
   tmpLbl.Initialize(internalPage, ID, Text, Size, False, theme)
   tmpLbl.IsPrintable = isPrintable
   tmpLbl.Visibility = Visibility
   Return tmpLbl
End Sub
...

It will never be able to print everything of course (e.g. scrolling components), but I’m pretty happy with the things it will be able to do.  Browser support for report printing is still in its infancy I’m afraid and it will take some time before all browsers are lined up supporting all HTML printing features.

But, together with the new ABMreport component, you will already be able to create reports with very little effort using ABMaterial and B4J!

ABMaterial 3.75 will be available for donators in a couple of weeks.

Alwaysbusy

Click here to Donation and support ABMaterial

B4J: ABMaterial 3.20 Public/3.50 Donators Released!

ABMB4JABMaterial 3.50 for B4J introduces Drag & Drop of ABMContainers between Cells! This new feature took a couple of weeks to implement, but I’m proud of the result.
It opens up a whole new range of possibilities for Web Apps.

Not many bug fixing needed in this release. Proves ABMaterial is really stable and truly production ready! (Happy face)

ABMGoogleMap also adds the possibility to use marker clusters, which cleans up your google map if you have many markers in one map.

GM2

Head to the ABMaterial section on this blog to get your free copy of 3.20, or make a small donation to support the development and get the latest 3.50 version + 9 extra libraries and tools to use in your own projects!

Have a great summer!

Alwaysbusy

Click here to Donation and support ABMaterial

 

B4J: New in ABMaterial 3.50: Drag and Drop about anything

ABMDragDrop

It may have been a bit silent the last few weeks around ABMaterial, but behind the scenes I have been working nights and weekends on maybe the biggest new feature since Chipmunk was introduced: Drag & Drop!

In 3.50, it will be possible to drag ABMContainers (and hence about anything in it) from one cell to another.

Let’s watch a video demonstration first:

Pretty cool hè!

How it works in B4J:
I explain the basics here, the DragDrop demo project included in the ABMaterial 3.50 download will show all tricks shown in the demo video.

1. Page_ParseEvent() gets a little bit of extra code to sync the moves made in the browser with our server

...
If SubExists(Me, eventName) Then
     Params.Remove("eventname")
     Params.Remove("eventparams")
     ' BEGIN NEW DRAGDROP
     If eventName = "page_dropped" Then
       page.ProcessDroppedEvent(Params)
     End If
     ' END NEW DRAGDROP
     Select Case Params.Size
     ...

2. Next we define a ‘DRAG’ group:

' name, minimum height of a cell
page.DragDropCreateGroup("DRAG", 150)

3. We add cells to this ‘DRAG’ group we want to drag & drop between. The last ‘null’ parameter can be any ABMComponent you want to use a title of the cell (e.g. the COLUMN X ABMLabel in the demo).

page.DragDropAddZone("DRAG", "DRAGCELL1", page.Cell(3,1),null)
page.DragDropAddZone("DRAG", "DRAGCELL2", page.Cell(3,2),null)
page.DragDropAddZone("DRAG", "DRAGCELL3", page.Cell(3,3),null)

4. We add our draggable ABMContainers to the cells. Note: they MUST be added with AddArrayComponent!

Here we also enable them to be draggable with cont.EnableDragDropZone().  There is also a shortcut method to enable/disable all ‘Zones/Cells’ in a group with cont.EnableDragDropAllZonesFromGroup() in one go.

page.Cell(3,1).AddArrayComponent(CreateCard("1", True, True, True, "I may be dragged everywhere", "../images/list1.jpg"), "Cards")
...

Sub CreateCard(ID As String, AllowInCell1 As Boolean, AllowInCell2 As Boolean, AllowInCell3 As Boolean, Title As String, image As String) As ABMContainer
  Dim cont As ABMContainer
  cont.Initialize(page, ID, "")
  cont.AddRows(1,True,"").AddCells12(1, "")
  cont.BuildGrid

  cont.IsTextSelectable = False

  If AllowInCell1 Then
    ' can be dragged to zone 1 of 'DRAG'
    cont.EnableDragDropZone("DRAG","DRAGCELL1",True)
  End If
  If AllowInCell2 Then
    ' can be dragged to zone 2 of 'DRAG'
    cont.EnableDragDropZone("DRAG","DRAGCELL2",True)
  End If
  If AllowInCell3 Then
    ' can be dragged to zone 3 of 'DRAG'
    cont.EnableDragDropZone("DRAG","DRAGCELL3",True)
  End If
  cont.EnableDragDropZone("DRAG","DRAGCELL4",True) ' for the sidebar

  Dim card As ABMCard
  card.InitializeAsCard(page, "card", "MY CARD", Title, ABM.CARD_SMALL, "")
  card.Image = image
  card.AddAction("Press me")

  ' add the card to the page
  cont.Cell(1,1).AddComponent(card)
  Return cont
End Sub

5. There are three events you will receive (all on Page level):

Sub Page_DragStart(component As String, source As String)
  Log("Drag start: " & component)
End Sub

Sub Page_DragCancelled(component As String, source As String)
  Log("Drag cancelled: " & component)
End Sub

' there are four parameters: component, source , target, before
Sub Page_Dropped(Params As Map)
  Log("Dropped: " & Params.Get("component"))
End Sub

And this is basically it! Very simple without having to write any HTML, CSS, PHP or Javascript. All done in pure B4J!

I’m finishing up ABMaterial 3.50, ABMonitor (an upcoming article on this for B4J and B4A soon on the blog) and ABTreeTableView now and early next week I think I may be able to release it to the donators.

Cheers,

Alwaysbusy

Click here to Donation and support ABMaterial

B4X: ABMaterial 3.03 Public/3.20 Donators released

A lot of internal changes were made in the ABMaterial 3.20 ‘Chipmunk’ release.  For example, ABMContainer has been rewritten from the ground-up, but with minimal impact to your existing projects.  It allows adding them as array components, and has much shorter ID tags in the generated HTML.

Mindful and Stanmiller had another go on the cache issues.  They came up with a very nice solution (I had tears in my eyes) and both have done extensive testing on this subject. A big thanks again!  ABM 3.20 is backwards compatible with the previous 3.0.3 Cache System, but I would advice you to make the changes whenever you have the chance.

A nice new feature for mobile devices is the New Debug feature using a modified version of Conzole!

New Debug at work:

Using this new feature is very simple in B4J:

First, you have to enable the console in BuildPage():

' params are: open on load, width
page.DebugConsoleEnable(True, 300)

Two methods can be used to show or hide the console programmaticaly. Can be handy as a buid-in tool in your apps where the user enters a code on his device and you can then check the console.

page.DebugConsoleShow
page.DebugConsoleHide

Now you can start doing some cool stuff 🙂 Some examples:

Show some text, or a server value (note: strings must be between single quotes!)

page.DebugConsoleLog("'Server click at: " & DateTime.Time(DateTime.Now) & "'") ' shows the text

Now I generated an error on purpose, so the demo can show it catches the error:

page.DebugConsoleLog("'value: ' + $('#pi002-r2c1').html();") ' generates an error, the ; at the end is wrong

Same code, but with the correct javascript syntax, showing the html() value of the tag:

page.DebugConsoleLog("'value: ' + $('#pi002-r2c1').html()") ' generates the html value of the pi002-r2c1 tag

It works also on the desktop, although you probably will use the Chrome console as you can do more.

But there is more. You can also redirect all browser console message directly into the B4J log window!  You can use the following commands anywhere in your own code to start/stop receiving the browser console messages.

page.DebugConsoleB4JStart
page.DebugConsoleB4JStop

This is a very powerful feature if you develop Webapps for mobile devices.  It can be done otherwise, but it is a pain in the a$$ to set it up. Now, you can easily debug it on your iPhone, iPad, Android or any other device.

ABMaterial 3.20 has over 20 new features and bug fixes.  Following the same philosophy as B4X, the quick release cycle has proven to work very well for ABMaterial and its many users.  Donators will receive their download mail shortly.  The public version 3.03 can be downloaded from this blog too.

I’m also glad that with these releases, now everyone can work with ‘Chipmunk‘, and this does also mean the development cycle for ‘Butterfly‘  has ended.

I would like to take this opportunity to thank everyone for the support, hourless testing and inventive ideas you came up with in the last few years. You’ve made ABMaterial one hell of a WebApp Development Tool!

ThankYou

But we are not at the end! Far from it as I already have big ideas for the next releases.

Until then, happy programming!

Alwaysbusy

Click here to Donation and support ABMaterial

B4J: New section and page navigation feature in ABMaterial (3.02)

PageNav2

ABMaterial 3.02 Maintenance Release will have a new feature, especially useful for ‘one page’ apps/websites to navigate. It intruduces ‘sections‘ within a page. A section is a logical grouping of rows, which can have its own background and a new extra navigation method/menu.

Sections must be declared at the BuildPage() stage, and are typically created right after the page.BuildGrid() method.

First, let me show you how it works and then I’ll explain how this is archieved with just 12 lines of B4J code!

Video:

Code (creating the Theme):

theme.Page.AddSection("section1", ABM.COLOR_BLACK, "", ABM.COLOR_WHITE, "")
theme.Page.AddSection("section2", ABM.COLOR_BLACK, "", ABM.COLOR_BROWN, ABM.INTENSITY_LIGHTEN4)
theme.Page.AddSection("section3", ABM.COLOR_WHITE, "", ABM.COLOR_BLUE, ABM.INTENSITY_LIGHTEN3)
theme.Page.AddSection("section4", ABM.COLOR_WHITE, "", ABM.COLOR_GREY, ABM.INTENSITY_DARKEN3)

Code (Adding the sections & the navigation)

page.CreateSection("section1", 1,1,ABM.COLOR_WHITE, "", "", "", "","calc(100vh - 56px)", 56, 500, ABM.VISIBILITY_ALL,"Section1")
page.SetSectionNavigation("section1", "section2", "Go to Section 2", ABM.SECTION_BUTTONTYPE_DOWN, "section1", "Section 1" )
page.CreateSection("section2", 2,2,ABM.COLOR_GREEN, "", "../images/bg-1.jpg", ABM.CONTAINERIMAGE_REPEAT_NOREPEAT, ABM.CONTAINERIMAGE_POSITION_COVER, "calc(100vh - 56px)", 56, 500, ABM.VISIBILITY_ALL, "Section2")
page.SetSectionNavigation("section2", "section3", "Go to Section 3", ABM.SECTION_BUTTONTYPE_DOWN, "section2", "Section 2")
page.CreateSection("section3", 3,3,ABM.COLOR_BLUE, "", "", "", "", "calc(100vh - 56px)", 56, 500, ABM.VISIBILITY_ALL,"Section3")
page.SetSectionNavigation("section3", "section4", "Go to Section 4", ABM.SECTION_BUTTONTYPE_DOWN, "section3", "Section 3")
page.CreateSection("section4", 4,4,ABM.COLOR_BLACK, "", "", "", "", "calc(100vh - 56px)", 56, 500, ABM.VISIBILITY_ALL, "Section4")
page.SetSectionNavigation("section4", "section1", "Back to Section 1", ABM.SECTION_BUTTONTYPE_UP, "section4", "Section 4")

In this example each section has only a height of 1 row (hence the from row 1 to row 1, from row 2 to row 2,…)

But, this can be any size, e.g. from row 5 to row 25. Something like:

page.CreateSection("section5", 5,25,ABM.COLOR_BLACK, "", "", "", "", "calc(100vh - 56px)", 56, 500, ABM.VISIBILITY_ALL, "Section4")

NOTE: Sections cannot be nested or overlapping!

The tricky part are the minHeight and scrollTop params. In the above examples, I’ve set the minHeight to the browsers height, minus the height of the TopBar: “calc(100vh – 56px)”.
For scrollTop, I’ve set it the the height of the TopBar so the top of the section is right under the navigation bar: 56

ABMaterial 3.02 Maintenance Release will go out in acouple of days to the donators with 25 bug fixes and implemented wishes!

Alwaysbusy

Click here to Donation and get the latest ABMaterial first!

B4J: ABMaterial Facebook-like timeline in less than 200 lines!

ABMFacebook50

I recently had a question if it was possible to create a Facebook-like WebApp with ABMaterial for B4J. I had some time to spare tonight (yeah right):rolleyes: and decided to give it a go…

All I needed were a couple of ABMContainers and the new CloseContent/OpenContent feature introduced in 3.02. The existing NextContent system would provide the infinite page.

It also uses a ABMCustomComponent for the ‘Like’ feature (I based it on this project), and you can find the source code of the class under the video:

About an hour, and 200 lines of B4J code later, I had a working timeline!  A real project would of course need some way to add an article, save them in the database, user management, etc but this is out of scope of this article.

Note: This example will be included in the upcoming 3.02 Maintenance Update, but I would like to share the source source code for the ‘Like’ functionality beforehand.

Source code for the ‘Like’ custom component.
You’ll need this zip file for the images and js/css files. Put the images and css in the /www/css/custom folder, the js file in the /www/js/custom/ folder.

The class LikeComponent:

'Class module
Sub Class_Globals
   Public ABMComp As ABMCustomComponent
   Public Counter As Int
End Sub

'Initializes the object. Countries and data are Json Strings
Public Sub Initialize(InternalPage As ABMPage, ID As String, AlreadyLiked As Int)
   ABMComp.Initialize("ABMComp", Me, InternalPage, ID)
   Counter = AlreadyLiked
End Sub

Sub ABMComp_Build(internalID As String) As String
   Return $"
<div id="${internalID} class="facebook-reaction"><!-- container div for reaction system -->
        <span class="like-btn"> <!-- Default like button -->
          <span class="like-btn-emo like-btn-default"></span> <!-- Default like button emotion-->
           <span class="like-btn-text">Like</span> <!-- Default like button text,(Like, wow, sad..) default:Like  -->
<ul class="reactions-box"> <!-- Reaction buttons container-->
	<li class="reaction reaction-like" data-reaction="Like"></li>
	<li class="reaction reaction-love" data-reaction="Love"></li>
	<li class="reaction reaction-haha" data-reaction="HaHa"></li>
	<li class="reaction reaction-wow" data-reaction="Wow"></li>
	<li class="reaction reaction-sad" data-reaction="Sad"></li>
	<li class="reaction reaction-angry" data-reaction="Angry"></li>
</ul>
</span>
<div class="like-stat"> <!-- Like statistic container-->
           <span class="like-emo"> <!-- like emotions container -->
             <span class="like-btn-like"></span> <!-- given emotions like, wow, sad (default:Like) -->
           </span>
           <span class="like-details">${Counter} others</span></div>
</div>
"$
End Sub

Sub ABMComp_FirstRun(InternalPage As ABMPage, internalID As String)
   Dim script As String = $"$("#${internalID} .reaction").on("click",function(){  // like click
     var data_reaction = $(this).attr("data-reaction");
     $("#${internalID} .like-details").html("You and ${Counter} others");
     $("#${internalID} .like-btn-emo").removeClass().addClass('like-btn-emo').addClass('like-btn-'+data_reaction.toLowerCase());
     $("#${internalID} .like-btn-text").text(data_reaction).removeClass().addClass('like-btn-text').addClass('like-btn-text-'+data_reaction.toLowerCase()).addClass("active");;

     if(data_reaction == "Like") {
         $("#${internalID} .like-emo").html('<span class="like-btn-like"></span>');
     } else {
         $("#${internalID} .like-emo").html('<span class="like-btn-like"></span><span class="like-btn-'+data_reaction.toLowerCase()+'"></span>');
     }
     var json = {'target': '${internalID}', 'like': data_reaction};
     b4j_raiseEvent('likecomponent_liked', json);
     });    

     $("#${internalID} .like-btn-text").on("click",function(){ // undo like click
     if($(this).hasClass("active")){
       $("#${internalID} .like-btn-text").text("Like").removeClass().addClass('like-btn-text');
       $("#${internalID} .like-btn-emo").removeClass().addClass('like-btn-emo').addClass("like-btn-default");
       $("#${internalID} .like-emo").html('<span class="like-btn-like"></span>');
       $("#${internalID} .like-details").html("${Counter} others");
       var json = {'target':'${internalID}'};
         b4j_raiseEvent('likecomponent_unliked', json);
       }
     })"$

   InternalPage.ws.Eval(script, Array As Object(ABMComp.ID))
   ' flush not needed, it's done in the refresh method in the lib
End Sub

Sub ABMComp_Refresh(InternalPage As ABMPage, internalID As String)

End Sub

' do the stuff needed when the object is removed
Sub ABMComp_CleanUp(InternalPage As ABMPage, internalID As String)

End Sub

Usage:

Sub Class_Globals
   ...
   Dim myLike As LikeComponent
   ...
End Sub

' adding the component
myLike.initialize(page, ID & "Like", Rnd(20, 500))
myContainer.Cell(4,1).AddComponent(myLike.ABMComp)

Events (the name LikeComponent is set in the FirstRun() method of the class:

public Sub LikeComponent_Liked(value As Map)
   Log(value.Get("like") & " ----> " & value.Get("target"))
End Sub

public Sub LikeComponent_UnLiked(value As Map)
   Log("Unliked ----> " & value.Get("target"))
End Sub

Et voila,  a nice little demonstration of what you can make using B4J and ABMaterial.

I’ll end with a little word of advice:

If you only plan to learn one programming language this year, B4X is definitely the one you should pick!  With ABMaterial, I give you the power to write responsive, professional WebApps, and with the other B4X tools you write native Android, iOS, Desktop (Mac, Windows, Linux), Raspberry Pi and Arduino apps.  All with the same language!  You’ll wonder why you ever bothered looking at all those other pricy programming tools out there…

Happy programming!

Alwaysbusy

Click here to Donation and get the latest ABMaterial first!

B4J: Msgbox and Inputbox in ABMaterial 2.51

upload_2017-2-12_12-0-6Sometimes you just need to show a quick Msgbox or a single-field Inputbox to the user. In ABMaterial for B4J you have probably, just like me, already made a couple of modal sheets to do this.

But in 2.51 I’ve integrated the possibility for such cases just to do it in one line of code (like a normal msgbox/inputbox).

It replaces the default looking ‘alert’ box from JavaScript to a more modern looking box.  The SweetAlert2 library, which is build on the orginal SweetAlert one, has been extended for ABMaterial so you can ‘Theme’ about anything you want.

For example, to show the msgbox you see on top of this article, this is the code you need to write:

page.Msgbox("MSGBOX1", "This is a very simple msgbox", "Msgbox()", "OK", "")

Some more examples:

theme.AddMsgBoxTheme("image")
theme.MsgBox("image").ConfirmButtonColor = ABM.COLOR_GREEN
theme.MsgBox("image").CancelButtonColor = ABM.COLOR_RED
theme.MsgBox("image").SetImage("https://unsplash.it/400/200", 400, 200)
theme.MsgBox("image").BackColor = ABM.COLOR_BLUEGREY
theme.MsgBox("image").BackColorIntensity = ABM.INTENSITY_LIGHTEN5

page.Msgbox2("MSGBOX3", "This is an msgbox with an image using a {C:#00AA00}theme{/C}!", "Msgbox2() 2", "OK","CANCEL",True, ABM.MSGBOX_TYPE_WARNING, "image")

upload_2017-2-12_12-2-24

page.InputBox("INPUTBOX1", "This is an inputbox for text.","OK", "CANCEL", True, ABM.INPUTBOX_TYPE_QUESTION, ABM.INPUTBOX_QUESTIONTYPE_TEXT,"","Type some text...", "", "Please enter something!", "")

upload_2017-2-12_12-3-30

page.InputBox("INPUTBOX3", "This is an inputbox for a password.","OK", "CANCEL", True, ABM.INPUTBOX_TYPE_QUESTION, ABM.INPUTBOX_QUESTIONTYPE_PASSWORD,"","Type your password...", "", "Please enter a password", "")

upload_2017-2-12_12-3-43

page.InputBox("INPUTBOX6", "This is an inputbox with a radio selection.","OK", "CANCEL", True, ABM.INPUTBOX_TYPE_QUESTION, ABM.INPUTBOX_QUESTIONTYPE_RADIO,"#00ff00", "","{'#ff0000': 'Red','#00ff00': 'Green','#0000ff': 'Blue'}", "You must select a color", "")

upload_2017-2-12_12-4-43

page.InputBox("INPUTBOX7", "This is an inputbox with a checkbox.","OK", "CANCEL", True, ABM.INPUTBOX_TYPE_QUESTION, ABM.INPUTBOX_QUESTIONTYPE_CHECKBOX,"", "I agree with the terms and conditions","", "Please agree with the conditions", "")

upload_2017-2-12_12-5-51

Events:

Sub page_MsgboxResult(returnName As String, result As String)
   myToastId = myToastId + 1
   page.ShowToast("toast" & myToastId, "toastgreen", returnName & " " & result, 5000)
End Sub

Sub page_InputboxResult(returnName As String, result As String)
   myToastId = myToastId + 1
   page.ShowToast("toast" & myToastId, "toastred", returnName & " " & result, 5000)
End Sub

ABMaterial Mainenance Release 2.51 will be available for the donators in the following days.

Alwaysbusy

Click here to Donation and get the latest ABMaterial first!

B4J: Custom component ABMGridTable (Team Project)

One of the most asked for components in ABMaterial is an editable Grid.  We already have ABMTable and ABMTableMutable, but they have their limitations when it comes to being editable. So, as this kind of object is huge, I suggest we make it a Team Effort to build one.

I did have a look around and found a nice one jsGrid which is feature rich, easy to use and fits rather well with the ABMaterial look and feel.

I already did the first part: modified the default CSS so it works with ABMaterial (checkboxes and combos did not work), created a basic class template and a sample on how to use it in a webapp.

Copy the CSS and JS files from the zip the css/custom/ and /js/custom/ folders of the ABMaterial www folder.
Start with localhost:51042/GridDemo

My current result looks like this (allows adding, deleting, changing, sorting and filtering):

upload_2017-2-6_12-34-40

Creating even such a complex component is very easy in B4J with ABMaterial.  Lets dive into some code:

The ABMTableGrid class

'Class module
Sub Class_Globals
	Public ABMComp As ABMCustomComponent
	Private cmbCountries As String
	Private DBdata As String
End Sub

'Initializes the object. Countries and data are Json Strings
Public Sub Initialize(InternalPage As ABMPage, ID As String, Countries As String, data As String)
	ABMComp.Initialize("ABMComp", Me, InternalPage, ID)
	cmbCountries = Countries
	DBdata = data
End Sub

Sub ABMComp_Build(internalID As String) As String
	Return $"
<div id="${internalID}"></div>
<script>var _${internalID};</script>"$
End Sub

Sub ABMComp_FirstRun(InternalPage As ABMPage, internalID As String)
	Dim script As String = $"_${internalID} = $("#${internalID}").jsGrid({
		height: "100%",
		width: "100%",
		filtering: true,
		inserting: true,
		editing: true,
		sorting: true,
		paging: true,
		autoload: true,
		pageSize: 10,
		pageButtonCount: 5,
		deleteConfirm: "Do you really want to delete the client?",
		controller: {
			loadData: function(filter) {
				return $.grep(${DBdata}, function(client) {
					var fName = filter.Name.toUpperCase();
					var fAddress = filter.Address.toUpperCase();
					return (!filter.Name || client.Name.toUpperCase().indexOf(fName) > -1)
						&& (!filter.Age || client.Age === filter.Age)
						&& (!filter.Address || client.Address.toUpperCase().indexOf(fAddress) > -1)
						&& (!filter.Country || client.Country === filter.Country)
						&& (filter.Married === undefined || client.Married === filter.Married);
					});
			},
			insertItem: function(insertingClient) {
				var json = JSON.stringify(insertingClient);
				b4j_raiseEvent('${ABMComp.ID}_inserted', {'value':json});
			},
			updateItem: function(updatingClient) {
				var json = JSON.stringify(updatingClient);
				b4j_raiseEvent('${ABMComp.ID}_updated', {'value':json});
			},
			deleteItem: function(deletingClient) {
				var json = JSON.stringify(deletingClient);
				b4j_raiseEvent('${ABMComp.ID}_deleted', {'value':json});
			}
		},
		fields: [
			{ name: "Name", type: "text", width: 150 },
			{ name: "Age", type: "number", width: 50 },
			{ name: "Address", type: "text", width: 200 },
			{ name: "Country", type: "select", items: ${cmbCountries}, valueField: "Id", textField: "Name" },
			{ name: "Married", type: "checkbox", title: "Is Married", sorting: false},
			{ type: "control" }
		]
	});"$

	InternalPage.ws.Eval(script, Array As Object(ABMComp.ID))
	' flush not needed, it's done in the refresh method in the lib
End Sub

public Sub SetDBData(data As String)
	DBdata = data
End Sub

Sub ABMComp_Refresh(InternalPage As ABMPage, internalID As String)
	Dim script As String = $"$("#${internalID}").jsGrid("refresh");"$
	InternalPage.ws.Eval(script, Null)
End Sub

Usage in a WebApp:

Sub Class_Globals
	...
	Dim myTableGrid As ABMTableGrid
	' fake database for demo purposes
	Dim database As Map
End Sub

' make sure you copied the files from the .zip in the right folders
public Sub BuildPage()
	...
	page.AddExtraCSSFile("custom/jsgrid.min.css")
	page.AddExtraCSSFile("custom/jsgrid-theme.min.css")
	page.AddExtraJavaScriptFile("custom/jsgrid.min.js")
	...
End Sub

public Sub ConnectPage()
	...
	' as for the demo, we fake a real database here
	LoadFakeData

	page.Cell(2,1).SetFixedHeight(500)

	' for our combo, we add a Json string counting the countries
	myTableGrid.Initialize(page, "myTableGrid", $"[
		{ Name: "", Id: 0 },
		{ Name: "United States", Id: 1 },
		{ Name: "Canada", Id: 2 },
		{ Name: "United Kingdom", Id: 3 },
		{ Name: "France", Id: 4 },
		{ Name: "Brazil", Id: 5 },
		{ Name: "China", Id: 6 },
		{ Name: "Russia", Id: 7 }
	]"$, LoadData)
    page.Cell(2,1).AddComponent(myTableGrid.ABMComp)
	...
End Sub

Sub LoadData() As String
	' we have to return a Json string
	Dim data As StringBuilder
	data.Initialize
	data.Append("[")
	For i = 0 To database.size - 1
		If i > 0 Then
			data.Append(",")
		End If
		data.Append(database.get(i))
	Next
	data.Append("]")
	Return data.ToString
End Sub

Sub myTableGrid_Inserted(value As Map)
	log(value.get("ID")
End Sub

Sub myTableGrid_Updated(value As Map)
	log(value.get("ID")
End Sub

Sub myTableGrid_Deleted(value As Map)
	log(value.get("ID")
End Sub

Sub LoadFakeData()
	database.Initialize
	database.Put(0,$"{"ID": 0,"Name": "Otto Clay","Age": 61,"Country": 6,"Address": "Ap #897-1459 Quam Avenue","Married": false}"$)
	database.Put(1,$"{"ID": 1,"Name": "Connor Johnston","Age": 73,"Country": 7,"Address": "Ap #370-4647 Dis Av.","Married": false}"$)
	...
End Sub

As you can see, adding this rather complex component took me about 10 minutes to make, literally the time to put on a pot of Java. (pun intended!)

So this is were you guys can come in!

Some suggestions:
1. Adapt my class so the fields are generic (maybe by passing a Map with field definitions in initialize and then ‘build’ the correct JavaScript string in FirstRun?).
2. I only have set some basic parameters (like sorting, filtering etc), but this component has a lot more to offer. They can become properties of the class. See the jsGrid link at the top of this article.

Download the full source code and share your changes in this B4J Forum topic, so everyone can benefit from your creation.

Alwaysbusy

Click here to Donation if you like my work

B4J: ABMaterial 2.20 Public/2.50 Donators released

abmaterial250

This is just an announcement for the new version(s) of ABMaterial for B4J! It has been a while since I’ve been able to release a new free (public) version, but it is worth the wait!

As the donators already know, 2.20 was a major release, as it tackled a whole new reconnecting system.  If you’re using the free version (pre 2.00), make sure you read the included documents to see how you need to upgrade to the new version!

It was also the first version that introduced the Grid Builder.  With this tool, it is now very easy to create your responsive framework.

2.50 containes a lot of new stuff, as you’ve read in the posts on this blog: ABMChat, ABMPlanner and ABMPercentSlider are only a few of the new things.  Worth mentioning are the major changes in the ABMPivot component, adding new features like exporting to Excel, collapsable rows and columns, new presentations and so on.

Check out the online demo abmaterial.com or download your free copy from the B4J website.

Donators will receive a download link for 2.50 shortly.

Happy Programming!

Alwaysbusy

Click here to Donation if you like my work

B4J: New ABMPlanner and ABMPercentSlider (ABMaterial 2.50)

abmplannerA friend of mine, who is a doctor, wanted me to write a scheduling webapp. He had some perticular requests:

1. You should be able to schedule per 5 minutes
2. But still need to be able to see the whole week
3. Wanted only to see the 5 working days (no weekends)
4. Should see from 06:00 to 21:00
5. As his assistant has to ‘fill’ the schedule, he also wanted somehow to show her when he would not be available.

I told him this quickly added up to showing about 840 possible appointments I had to show him on his desktop, tablet and iPhone. “Yes”, was his dry answer…

I didn’t know any such object, so I’ve put on my thinking hat and came up with ABMPlanner for B4J!

Although it has been written especially for his needs, I’m sure it can come in handy for other kind of projects too. For example you can schedule per 5, 10, 15, 20 or 30 minutes. You can set the start/end time of a day. And it can show a full week (including the weekend). You can give each task different colors etc…

It can also show the week as a ‘HeatMap’, so he can see very fast where he can put someone in-between other patients.

The second component ABMPercentSlider (in the video when the modalsheet is opened) is an alternative radio group component. It ranges the possibilities in a ‘percentage’ range, with 5% intervals.

But have a look at the video (app is work in progress and sorry it is in dutch). It can be intimidating at first glance, but once you ‘get’ it, it’s actually a breeze to schedule stuff.

Some relevant code:

BuildPage:

Dim plan As ABMPlanner
plan.Initialize(page, "plan", False, 6,20, 5,False,"black")
plan.UseHeatMap("1,3,6")
page.Cell(2,1).AddComponent(plan)

Adding a task (loading a random generated week):

Sub LoadRandomWeek(StartDay As Long,doRefresh As Boolean)
   Dim plan As ABMPlanner = page.Component("plan")
   plan.ClearPlanner

   Dim Days(7) As String
   Dim Day As Int = DateTime.GetDayOfMonth(StartDay)
   Dim nextDay As Long = StartDay
   Dim perNext As Period
   perNext.Initialize
   perNext.Days = 1
   For i = 0 To 6
     If ActiveDay = -1 Then
       If DateTime.Date(nextDay) = DateTime.Date(DateTime.Now) Then
         ActiveDay = i
       End If
     End If
     Days(i)   = Day
     If Days(i).Length = 1 Then
       Days(i) = "0" &amp; Days(i)
     End If
     nextDay = DateUtils.AddPeriod(nextDay, perNext)
     Day = DateTime.GetDayOfMonth(nextDay)
   Next
   Dim mins As List
   mins.Initialize
   mins.Add(1)
   mins.Add(3)
   mins.Add(6)
   mins.Add(12)
   plan.SetDayLabels("MA " &amp; Days(0), "DI " &amp; Days(1), "WO " &amp; Days(2), "DO " &amp; Days(3), "VR " &amp; Days(4),  "ZA " &amp; Days(5),  "ZO " &amp; Days(6))
   Dim PerTotal As Int = 12

   For i = 0 To 4
     Dim u As Int = 6
     Dim m As Int = 0
     Dim l As Int = 0
     Dim lIndex As Int
     Do While u &lt; 21
       Do While m &lt; PerTotal    
         m = m + Rnd(1,3)          
         lIndex = Rnd(0,3)
         l = mins.Get(lIndex)
         If (u*PerTotal + m + l) &lt; (21*PerTotal) And m &lt; PerTotal And (i &lt;&gt; 2 Or u &lt;=11 ) Then
           Dim patient As String = FirstNames.get(Rnd(0,19)) &amp; " " &amp; LastNames.get(Rnd(0,19))
           counter = counter + 1
           plan.AddTask(NewTask(i, "T" &amp; counter,u,m,l,patient,lIndex)) ' taskid must be a string!                
         End If
         m = m + l
       Loop
       m = m - PerTotal
       u = u + 1
     Loop
   Next

   plan.SetActiveDay(ActiveDay)
   For i = 12 To 20
     plan.SetHourStatus(2,i,False)
   Next
   If doRefresh Then
     plan.Refresh
   End If
End Sub

Events:

Sub plan_ActiveDayChanged(day As Int)
   Log("New day: " &amp; day)
   ActiveDay = day
   ' important to sync the server with the browser!
   Dim plan As ABMPlanner = page.Component("plan")
   plan.SetActiveDay(ActiveDay)
End Sub

Sub plan_MinutesClicked(Value As String)
   Dim spl() As String = Regex.Split(";", Value)
   ' spl(0) is here always 0, as this would contain the menu item clicked.  This is to be uniform with the MenuClicked Value param
   Log("Day: " &amp; spl(1))
   Log("Hour: " &amp; spl(2))
   Log("MinPer5: " &amp; spl(3))
   ' use the ABM.PLANNER_STATUS_ constants to check the status
   Log("Status: " &amp; spl(4))
   Log("Task ID: " &amp; spl(5))

   page.ShowModalSheet("patientafspraak")
End Sub

Sub plan_MenuClicked(MenuType As String, Value As String)
   Dim spl() As String = Regex.Split(";", Value)
   Dim plan As ABMPlanner = page.Component("plan")
   Log("MenuType: " &amp; MenuType)
   Log("Value: " &amp; Value)
   Select Case MenuType
     Case ABM.PLANNER_MENUTYPE_DAY
       ' update the database

       Log("Day: " &amp; spl(1))

       Select Case spl(0)
         Case ABM.PLANNER_MENU_SETFREE
           plan.SetDayStatus(spl(1), True)
         Case ABM.PLANNER_MENU_SETNOTAVAILABLE
           plan.SetDayStatus(spl(1), False)
       End Select

       ' IMPORTANT to perform the action in the browser
       plan.PerfromDayHourMenuAction
     Case ABM.PLANNER_MENUTYPE_HOUR
       ' update the database

       Log("Day: " &amp; spl(1))
       Log("Hour: " &amp; spl(2))

       Select Case spl(0)
         Case ABM.PLANNER_MENU_SETFREE
           plan.SetHourStatus(spl(1), spl(2), True)
         Case ABM.PLANNER_MENU_SETNOTAVAILABLE
           plan.SetHourStatus(spl(1), spl(2), False)
       End Select

       ' IMPORTANT to perform the action in the browser
       plan.PerfromDayHourMenuAction  
     Case ABM.PLANNER_MENUTYPE_MIN
       ' update the database

       Log("Day: " &amp; spl(1))
       Log("Hour: " &amp; spl(2))
       Log("MinPer5: " &amp; spl(3))
       ' use the ABM.PLANNER_STATUS_ constants to check the status
       Log("Status: " &amp; spl(4))
       Log("Task ID: " &amp; spl(5))

       Select Case spl(0)
         Case ABM.PLANNER_MENU_CUT
           cutTaskID = spl(5)
           copyTask = plan.GetTask(spl(5))
           IsCut = True
           myToastID = myToastID + 1
           page.ShowToast("toast" &amp; myToastID, "toastred", "Verplaats " &amp; copyTask.Text &amp; " naar...", 5000)
         Case ABM.PLANNER_MENU_COPY
           copyTask = plan.GetTask(spl(5))
           IsCut = False
           myToastID = myToastID + 1
           page.ShowToast("toast" &amp; myToastID, "toastred", "Kopieer " &amp; copyTask.Text &amp; " naar...", 5000)
         Case ABM.PLANNER_MENU_PASTE
           If IsCut Then
             plan.RemoveTask2(cutTaskID)
           Else
             counter = counter + 1
             copyTask = copyTask.Clone("T" &amp; counter)
           End If
           copyTask.Day = spl(1)
           copyTask.StartHour = spl(2)      
           copyTask.StartMinututesPer = spl(3)
           plan.AddTask(copyTask)      
           plan.Refresh
           If IsCut Then
             myToastID = myToastID + 1
             page.ShowToast("toast" &amp; myToastID, "toastred", copyTask.Text &amp; " verplaatst!", 5000)
           Else
             myToastID = myToastID + 1
             page.ShowToast("toast" &amp; myToastID, "toastred", copyTask.Text &amp; " gekopieerd!", 5000)
           End If
         Case ABM.PLANNER_MENU_DELETE
           plan.RemoveTask2(spl(5))
           plan.Refresh
       End Select

   End Select
End Sub

These are the final components for ABMaterial 2.50. I’m now going to prepare everything for the next release. (updating the demos etc), so donators will receive these new toys shortly…

Alwaysbusy

Click here to Donation and support ABMaterial

 

 

B4J: New ABMChat component for ABMaterial 2.50

abmchat

ABMChat is a new cool looking ABMaterial component to create a Web based chat program. The inner workings are very much like Erels ChatRoom, but with a slick interface. This can be a useful control if for example you want to give chat-like support within you own B4J webapp.

Some features:
– Balloon/Bubble-like chat
– Support for some ASCII emoji
– IsTyping ballon

Coding this with B4J and ABMaterial is very easy:

Code ChatShared module:

Sub Process_Globals
   Public AvoidDuplicates As Map
   Private connections As Map
   Public LastMessages As List
   Private IsTypingMap As Map
End Sub

public Sub Init
   AvoidDuplicates = Main.srvr.CreateThreadSafeMap
   connections.Initialize
   LastMessages.Initialize
   IsTypingMap.Initialize
End Sub

public Sub NewConnection(page As ChatPage, pageID As String)
   DateTime.TimeFormat = "HH:mm"

   Dim IdentifyBubble As ABMChatBubble
   IdentifyBubble.Initialize("Server", page.ChatName & " has joined the chat...", "Server " & DateTime.Time(DateTime.Now), "server", "server")
   JoinLeftMessage(pageID, IdentifyBubble)
   connections.Put(pageID, page)
   CallSubDelayed2(page, "CurrentConversation", LastMessages)
End Sub

public Sub NewMessage(pageID As String, Message As ABMChatBubble)
   LastMessages.Add(Message)
   For Each page As ChatPage In connections.Values
     CallSubDelayed2(page, "NewMessage", Message)
   Next
End Sub

public Sub JoinLeftMessage(pageID As String, Message As ABMChatBubble)
   For Each page As ChatPage In connections.Values
     CallSubDelayed2(page, "NewMessage", Message)
   Next
End Sub

public Sub IsTyping(pageID As String, Value As String)
   Dim typeBool As Boolean
   If Value = "" Then
     IsTypingMap.Remove(pageID.ToLowerCase)
   Else
     IsTypingMap.put(pageID.ToLowerCase, "true")
   End If
   If IsTypingMap.Size > 0 Then
     typeBool = True
   End If
   For Each page As ChatPage In connections.Values
     CallSubDelayed3(page, "IsTyping", pageID, typeBool)
   Next
End Sub

public Sub Disconnected(page As ChatPage, pageID As String)
   If connections.ContainsKey(pageID) = False Or connections.Get(pageID) <> page Then Return
   Dim LeaveBubble As ABMChatBubble
   LeaveBubble.Initialize("Server", page.ChatName & " has left the chat...", "Server " & DateTime.Time(DateTime.Now), "server","server")
   JoinLeftMessage(pageID, LeaveBubble)
   connections.Remove(pageID)
   AvoidDuplicates.Remove(pageID.ToLowerCase)
End Sub

Public Sub HasIdentified(pageID As String) As Boolean
   Return connections.ContainsKey(pageID)
End Sub

Relevant Code ChatPage class:

Private Sub WebSocket_Disconnected
   Log("Disconnected")
   If ABMPageId <> "" Then CallSubDelayed3(ChatShared, "Disconnected", Me, ABMPageId)
End Sub

public Sub BuildTheme()
   ' start with the base theme defined in ABMShared
   theme.Initialize("pagetheme")
   theme.AddABMTheme(ABMShared.MyTheme)

   ' add additional themes specific for this page
   theme.AddChatTheme("myChat")
   theme.Chat("myChat").AddBubble("server")
   theme.Chat("myChat").Bubble("server").Color = ABM.COLOR_BLACK
   theme.Chat("myChat").Bubble("server").TextColor = ABM.COLOR_WHITE

   theme.Chat("myChat").AddBubble("me")
   theme.Chat("myChat").Bubble("me").Color = ABM.COLOR_LIGHTBLUE
   theme.Chat("myChat").Bubble("me").TextColor = ABM.COLOR_WHITE

   theme.Chat("myChat").AddBubble("them")
   theme.Chat("myChat").Bubble("them").Color = ABM.COLOR_GREY
   theme.Chat("myChat").Bubble("them").ColorIntensity = ABM.INTENSITY_LIGHTEN2
   theme.Chat("myChat").Bubble("them").TextColor = ABM.COLOR_BLACK
End Sub

public Sub ConnectPage()
'   connecting the navigation bar    

'   init all your own variables (like a List, Map) and add your components
   Dim chat As ABMChat
   chat.Initialize(page, "conversation",600, 450, 255, "mychat")
   chat.AddBubble("Server", "I'm Server, what is your name?", "The Server", "server", "server")

   page.Cell(1,1).AddComponent(chat)

   Dim ChatInput As ABMInput
   ChatInput.Initialize(page, "ChatInput", ABM.INPUT_TEXT, "", False, "")
   ChatInput.PlaceHolderText = "Type your message here...."
   ChatInput.RaiseChangedEvent = True
   page.Cell(2,1).AddComponent(ChatInput)

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

Sub ChatInput_Changed(value As String)
   If ChatShared.HasIdentified(ABMPageId) Then
     CallSubDelayed3(ChatShared, "IsTyping", ABMPageId, value)
   End If
End Sub

Sub ChatInput_EnterPressed(value As String)
   DateTime.TimeFormat = "HH:mm"

   If ChatShared.HasIdentified(ABMPageId) Then
     Dim bubble As ABMChatBubble
     bubble.Initialize(ChatName, value, ChatName & " " & DateTime.Time(DateTime.Now), "me", "them")
     CallSubDelayed3(ChatShared, "NewMessage", ABMPageId, bubble)
     CallSubDelayed3(ChatShared, "IsTyping", ABMPageId, "")
   Else
     If value = "" Then Return
     If ChatShared.AvoidDuplicates.ContainsKey(value.ToLowerCase) Then
       Dim chat As ABMChat = page.Component("conversation")
       chat.AddBubble("Server", "Sorry, but " & value & " is already taken!", "The Server", "server", "server")
       chat.Refresh
     Else
       ChatName = value
       ChatShared.AvoidDuplicates.Put(value.ToLowerCase, value)
       Dim chat As ABMChat = page.Component("conversation")
       chat.SetMyFrom(ChatName)
       chat.AddBubble("Server", "Welcome to ABMChat " & ChatName & "!", "The Server", "server", "server")
       chat.Refresh
       CallSubDelayed3(ChatShared, "NewConnection", Me, ABMPageId)
     End If
   End If

   Dim ChatInput As ABMInput = page.Component("ChatInput")
   ChatInput.Text = ""
   ChatInput.Refresh
End Sub

public Sub NewMessage(bubble As ABMChatBubble)
   Dim chat As ABMChat = page.Component("conversation")
   chat.AddBubble2(bubble)
   chat.Refresh
   chat.ScrollToBottom   ' scroll to the end of the chat
End Sub

public Sub IsTyping(pageID As String, typeBool As Boolean)
   Dim chat As ABMChat = page.Component("conversation")
   If pageID <> ABMPageId Then
     If typeBool = False Then
       chat.ThemAreTyping(False, "") 'hide the 'is typing' bubble
     Else
       chat.ThemAreTyping(True, "") ' show the 'is typing' bubble if not yet shown
     End If
     chat.Refresh
     chat.ScrollToBottom   ' scroll to the end of the chat
   End If
End Sub

public Sub CurrentConversation(LastMessages As List)
   If LastMessages.Size > 0 Then
     Dim chat As ABMChat = page.Component("conversation")
     chat.SetConversation(LastMessages)
     chat.Refresh
     chat.ScrollToBottom   ' scroll to the end of the chat
   End If
End Sub

So, there you have it. A complete modern looking multichat webapp that runs on all platforms in just about 150 lines of code!

Alwaysbusy

Click here to Donation and support ABMaterial

B4J: ABMaterial 2.20 with ABMGridBuilder

gridbuilder1

Responsive design is great, but at the same time something that is not so easy to grasp as it works over different levels.  ABMaterial has been using this concept from the very beginning using Materialize CSS and to make it easier, I created a builder for it.

A great start was the implementation done for Bootstrap: Shoelace.io.  I liked the idea of ‘drawing’ your grid.  However, for ABMaterial, I did want more.

Objectives:

  1. Needed to generate B4J source code, not html
  2. I wanted a way to return B4J code back to the builder
  3. Some extra functionalities to insert cells before/after another
  4. Save/Load to re-use designs.
  5. The generated code should include a ‘description’ of the grid.
  6. A ‘light’ and a ‘dark’ theme for night owls like me

Here is a little demonstration on how to work with it:

It generates code something like this:

'PHONE
'╔═══════════════════════════════════════════════════════════════════════════════════╗
'║ 1,1                                                                               ║
'╠═══════════════════════════════════════════════════════════════════════════════════╣
'║ 2,1                       | 2,2                       | 2,3                       ║
'║-----------------------------------------------------------------------------------║
'║ 2,4                       | 2,5                       | 2,6                       ║
'║-----------------------------------------------------------------------------------║
'║ 2,7                       | 2,8                       | 2,9                       ║
'║-----------------------------------------------------------------------------------║
'║ 2,10                      | 2,11                      | 2,12                      ║
'╠═══════════════════════════════════════════════════════════════════════════════════╣
'║ 3,1                                                                               ║
'╠═══════════════════════════════════════════════════════════════════════════════════╣
'║ 4,1                                                                               ║
'╚═══════════════════════════════════════════════════════════════════════════════════╝

'TABLET
'╔═══════════════════════════════════════════════════════════════════════════════════════════════════════════╗
'║ 1,1                                                                                                       ║
'╠═══════════════════════════════════════════════════════════════════════════════════════════════════════════╣
'║ 2,1                      | 2,2                      | 2,3                      | 2,4                      ║
'║-----------------------------------------------------------------------------------------------------------║
'║ 2,5                      | 2,6                      | 2,7                      | 2,8                      ║
'║-----------------------------------------------------------------------------------------------------------║
'║ 2,9                      | 2,10                     | 2,11                     | 2,12                     ║
'╠═══════════════════════════════════════════════════════════════════════════════════════════════════════════╣
'║ 3,1                                                                                                       ║
'╠═══════════════════════════════════════════════════════════════════════════════════════════════════════════╣
'║ 4,1                                                                                                       ║
'╚═══════════════════════════════════════════════════════════════════════════════════════════════════════════╝

'DESKTOP
'╔═══════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════╗
'║ 1,1                                                                                                                               ║
'╠═══════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════╣
'║ 2,1      | 2,2      | 2,3      | 2,4      | 2,5      | 2,6      | 2,7      | 2,8      | 2,9      | 2,10     | 2,11     | 2,12     ║
'╠═══════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════╣
'║ 3,1                                                                                                                               ║
'╠═══════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════╣
'║ 4,1                                                                                                                               ║
'╚═══════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════╝

cont1.AddRows(1, True,"").AddCells12(1,"")
cont1.AddRows(1, True,"").AddCellsOS(12,0,0,0,4,3,1,"")
cont1.AddRows(2, True,"").AddCells12(1,"")
cont1.BuildGrid ' IMPORTANT!

This description of the grid is very handy when you start adding your components. As you can see in the video, copying the AddRows lines and parsing them back into the builder makes it easy to make changes.

The functionalities are all in the grid itself so you can work fast:

gridbuilder2

ABMGridBuilder is completely written in B4J, so it runs on all desktop platforms (Windows, OSX, Linux).

Tools like this make creating WebApps with ABMaterial and B4J even easier.  It is astonishing how versatile the B4X programming suite from Anywhere Software really is.  It is unique, fast, super stable and full of features nowhere else to be found. I’m so glad I discovered this great tool!

Donators will receive their mail with the download link shortly.

See ya!

Alwaysbusy

Click here to Donation and support ABMaterial

 

B4J: ABMaterial building a QR Code component

qrcodecomp

On the B4J forum, liulifeng77 asked the question how he could draw a QR Code in an ABMaterial web page.

I had 5 minutes to spare, so I created the QRCode component using ABMCustomComponent. It is using this javascript library: https://github.com/davidshimjs/qrcodejs

The component class CompQRCode:

'Class module
Sub Class_Globals
     Public ABMComp As ABMCustomComponent
     Public myText As String
   Public myWidth As Int
   Public myHeight As Int
   Public myColorDark As String
   Public myColorLight As String
   Public myCorrectLevel As String
End Sub
'Initializes the object. You can add parameters to this method if needed.
Public Sub Initialize(InternalPage As ABMPage, ID As String, text As String, width As Int, height As Int, colorDark As String, colorLight As String, correctLevel As String)
     ABMComp.Initialize("ABMComp", Me, InternalPage, ID)
     myText = text
   myWidth = width
   myHeight = height
   myColorDark = colorDark
   myColorLight = colorLight
   myCorrectLevel = correctLevel
End Sub
Sub ABMComp_Build(internalID As String) As String
  Return $"
<div id="${internalID}svg"/></div>
<script>var qrcode${internalID};</script>"$
End Sub
' Is useful to run some initalisation script.
Sub ABMComp_FirstRun(InternalPage As ABMPage, internalID As String)
   Dim script As String = $"qrcode${internalID} = new QRCode(document.getElementById("${internalID}svg"), {
  text: "${myText}",
  width: ${myWidth},
  height: ${myHeight},
  colorDark : "${myColorDark}",
  colorLight : "${myColorLight}",
  correctLevel : ${myCorrectLevel}
});"$
     InternalPage.ws.Eval(script, Array As Object(ABMComp.ID))
     ' flush not needed, it's done in the refresh method in the lib
End Sub
' runs when a refresh is called
Sub ABMComp_Refresh(InternalPage As ABMPage, internalID As String)
   Dim script As String = $"qrcode${internalID}.clear();
   qrcode${internalID}.makeCode("${myText}");"$
     InternalPage.ws.Eval(script, Array As Object(ABMComp.ID))
End Sub
' do the stuff needed when the object is removed
Sub ABMComp_CleanUp(InternalPage As ABMPage, internalID As String)
End Sub

Usage (if you want to change its text at run time, put the declaration in Class_globals):

Dim QRCode As CompQRCode

In BuildPage() load the javascript library:

page.AddExtraJavaScriptFile("custom/qrcode.min.js")

In ConnectPage():

QRCode.Initialize(page, "qrcode", "Alwaysbusy's ABMaterial", 128,128, "#000000", "transparent", "QRCode.CorrectLevel.H")
page.Cell(3,1).AddComponent(QRCode.ABMComp)

Et voila, another new component!

Alwaysbusy

Click here to Donation and support ABMaterial

B4J: ABMaterial Public 1.22/Donators 2.00 now released

It has been a long night to get everything ready, but I’m happy to release the new version(s) of ABMaterial for B4J!

2.00 is a major release.  Lots of new stuff and some changes that were really needed in preparation of the upcoming (ABMaterial Abstract Desinger) ABMAD tool.

Highlights of the releases:

1.22: New component ABMPatternLock

ABMPatternLock is yet another authentication method you can use based on the popular Pattern Lock you see in e.g. Android.

patternlock

1.22: ABMContainer can collapse

ABMContainer can be initialized as a collapsable component.  As a collapsable has a header and a body and clicking on the header will collapse/expand the body.  This can be used as an alternative for an ABMCard so you can build your own.

1.22: Support for Font-Awesome icons

If you set: page.UseFontAwesome = true then you can, next to the material icons, also use the Font Awesome Icon library (634 icons in used version Font Awesome 4.6.3)
See fontawesome.io for a list of icons.

fontawesome

2.00: New component ABMChronologylist

The ABMChronologyList is a vertical timeline component. Useful to give an overview of a limited period. It is device aware so e.g. on a phone, all items will be one under each other.

chronology

2.00: New helper ABMSideBar

In ABMaterial 2.00, you will be able to define multiple side bars (sliding in from the right). I’ve tried to mimic the usage of ABMSideBar as much of the existing Side Navigation menu. The tricky part was making it work well with the NavigationBar, on all screensizes.

Check out the online demo abmaterial.com or download your free copy from the B4J website.  Make sure you read the included README.TXT files.  It contains some important info and shows some tips and tricks.

That’s it for now.  Next big release will be all about ABMAD. Until next time!

Alwaysbusy

Click here to Donation if you like my work

B4J: Creating your own components in ABMaterial

abmcustgauge

Although ABMaterial has already a lot of components build-in, sometimes you just need a specific one.  I had a question on the B4J forum from CGP who needed a Gauge component.

So let’s build one! CGP had found a very nice library JustGage that would allow him to get the Gauge he wanted. Its value had to be updateable from within B4J, and this was a bit tricky. It looked like we have to be a bit creative because this javascript library did not connect its parent tag with the created JustGage.

We will have to keep track of the JustGage variable ourselves by adding a script tag in the build:

Sub ABMComp_Build(internalID As String) As String
  Return $"<div id="${internalID}"></div><script>var _${internalID};</script>"$
End Sub

The rest of the code is straight forward and we can just follow the javascript instructions and add them in our relative events.

An ABMaterial custom component has 4 important events:

ABMComp_Build(): This is where we have to add the html tag (anchor) for the new component.  In this particular case we also have to add a <script> tag that will hold our justGage variable.  We can then later use this variable to update the Gauge chart.

ABMComp_FirstRun(): Here we can add a script that will run, well, at first run. In most cases, this is where we add the libraries javascript initialization code. The B4J $””$ Smartstrings are in methods like this particulary handy. Use them!

ABMComp_Refresh(): The place where we can adjust properties of the javascript component.  Like for this component, we’ll adjust the value of the gauge.

ABMComp_CleanUp(): An event that is raised when the component is removed.  Could be useful to do some cleanup in the html of set javascript variables to null.

Here is the complete B4J code for our customGauge component:

Sub Class_Globals
   Public ABMComp As ABMCustomComponent
  Public myValue As Int
  Public myLabel As String
End Sub

'Initializes the object. You can add parameters to this method if needed.
Public Sub Initialize(InternalPage As ABMPage, ID As String, value As Int, name As String)
  ABMComp.Initialize("ABMComp", Me, InternalPage, ID)
  myValue = value
  myLabel = name
End Sub

Sub ABMComp_Build(internalID As String) As String
  Return $"<div id="${internalID}"></div><script>var _${internalID};</script>"$
End Sub

' Is useful to run some initalisation script.
Sub ABMComp_FirstRun(InternalPage As ABMPage, internalID As String)
  Dim script As String = $"_${internalID} = new JustGage({
  id: "${internalID}",
  value: ${myValue},
  Min: 0,
  Max: 100,
  relativeGaugeSize: true,
  title: "${myLabel}"
  });"$

  InternalPage.ws.Eval(script, Array As Object(ABMComp.ID))
  ' flush not needed, it's done in the refresh method in the lib
End Sub

' runs when a refresh is called
Sub ABMComp_Refresh(InternalPage As ABMPage, internalID As String)
  Dim script As String = $"_${internalID}.refresh(${myValue});"$
  InternalPage.ws.Eval(script, Null)
End Sub

' do the stuff needed when the object is removed
Sub ABMComp_CleanUp(InternalPage As ABMPage, internalID As String)

End Sub

Now, how do we use our new component in our B4J ABMaterial WebApp?

First, we’ll need to make our custGauge a global variable on the page:

Sub Class_Globals
  Dim custGauge As CustomGauge
  ...

Next, we add the justGauge libraries in the BuildPage().  We’ve placed them in \www\js\custom folder so we can add:

Sub BuildPage()
  ...
  page.AddExtraJavaScriptFile("custom/justgage.js")
  page.AddExtraJavaScriptFile("custom/raphael-2.1.4.min.js")
  ...

In the ConnectPage, we’ll create the actual Gauge and we add a button so we can change its value:

Sub ConnectPage()
  ...
  custGauge.Initialize(page, "custGauge", 25, "Test")
  page.Cell(7,1).AddComponent(custGauge.ABMComp)

  Dim custGaugebtn1 As ABMButton
  custGaugebtn1.InitializeRaised(page, "custGaugebtn1", "", "", "BUTTON", "")
   page.Cell(7,1).AddComponent(custGaugebtn1)
   ...

And finally we add out button code to adjust the value of the Gauge:

Sub custGaugebtn1_Clicked(Target As String)
   custGauge.myValue = Rnd(10,100)
   custGauge.ABMComp.Refresh
End Sub

And just like that, it looks like a fun new component we’ve got in B4J ABMaterial!

Until next time,

Alwaysbusy

Click here to Donation and support ABMaterial