Category Archives: 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

Or make a donation with Bitcoins:

BTC: 13x7b12GonCj6iphnjLuonrwURK2jCgMdE

This is my bitcoin address as a QR code. It is possible to send bitcoins to me from mobile wallets by scanning this code.

13x7b12GonCj6iphnjLuonrwURK2jCgMdE

Advertisements

B4XHelp: An ABMaterial WebApp for B4X Library documentation

b4xhelpI’ve created an ABMaterial (3.75) WebApp to show the documentation for the B4X libraries. Everyone who created a library for B4A, B4i or B4J can upload their .xml file to the WebApp and everyone can consult it. You can re-upload a new version for a library if needed.

Link: http://81.165.232.188:50912/b4xhelp/

HTTP/2 SSL Link: https://81.165.232.188:50911/b4xhelp/

b4xhelp

Notes:

  • This WebApp is open for everyone, but please try to keep it clean. Thank you!
  • B4XHelp is running on my personal Raspberry Pi with not a super-duper internet connection, so it also may give some indication how it handles a lot of users.
  • The libraries itself are NOT uploaded to the WebApp! Only the .xml files to parse them.
  • The WebApp also runs on a HTTP/2 server, but I do not have a verified certificate. If you use this link, you will get a warning that it may not be safe. You normally only need to accept this once:

priv

ABMaterial may be overwhelming at first with all its features at your disposal, but once you get the hang of it, creating such a WebApp as this, in pure B4J, can go very fast.

Alwaysbusy

Click here to Donation and support ABMaterial

Or make a donation with Bitcoins:

BTC: 13x7b12GonCj6iphnjLuonrwURK2jCgMdE

This is my bitcoin address as a QR code. It is possible to send bitcoins to me from mobile wallets by scanning this code.

13x7b12GonCj6iphnjLuonrwURK2jCgMdE

 

 


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

Or make a donation with Bitcoins:

BTC: 13x7b12GonCj6iphnjLuonrwURK2jCgMdE

This is my bitcoin address as a QR code. It is possible to send bitcoins to me from mobile wallets by scanning this code.

13x7b12GonCj6iphnjLuonrwURK2jCgMdE


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

Or make a donation with Bitcoins:

BTC: 13x7b12GonCj6iphnjLuonrwURK2jCgMdE

This is my bitcoin address as a QR code. It is possible to send bitcoins to me from mobile wallets by scanning this code.

13x7b12GonCj6iphnjLuonrwURK2jCgMdE


B4J: IMPORTANT change in ABMaterial for B4J 5.80+

As B4J 5.80 has been updated to Jetty 9.4.6 (which contains an important change in the internal Session framework), you will need to update some code in StartServer() and StartServerHTTP2() in the ABMApplication module.

In pre B4J 5.80 with Jetty 9.3.x it was:

Dim joServer As JavaObject = srvr
joServer.GetFieldJO("server").RunMethod("stop", Null)
joServer.GetFieldJO("context").RunMethodJO("getSessionHandler", Null).RunMethodJO("getSessionManager", Null).RunMethodJO("getSessionCookieConfig", Null).RunMethod("setMaxAge", Array(31536000)) ' 1 year

'NEW FEATURE! Each App has its own Session Cookie
joServer.GetFieldJO("context").RunMethodJO("getSessionHandler", Null).RunMethodJO("getSessionManager", Null).RunMethodJO("getSessionCookieConfig", Null).RunMethod("setName", Array(ABMShared.AppName.ToUpperCase))
joServer.GetFieldJO("server").RunMethod("start", Null)

must be changed to this in B4J 5.80+ with Jetty 9.4.6:

Dim joServer As JavaObject = srvr
joServer.GetFieldJO("server").RunMethod("stop", Null)
joServer.GetFieldJO("context").RunMethodJO("getSessionHandler", Null).RunMethodJO("getSessionCookieConfig", Null).RunMethod("setMaxAge", Array(31536000)) ' 1 year

' NEW FEATURE! Each App has its own Session Cookie
joServer.GetFieldJO("context").RunMethodJO("getSessionHandler", Null).RunMethodJO("getSessionCookieConfig", Null).RunMethod("setName", Array(ABMShared.AppName.ToUpperCase))
joServer.GetFieldJO("server").RunMethod("start", Null)

' + This setting is with reservation until confirmation
Dim secs As Long = ABMShared.CacheScavengePeriodSeconds ' must be defined as a long, else you get a 'java.lang.RuntimeException: Method: setIntervalSec not matched.' error
joServer.GetFieldJO("context").RunMethodJO("getSessionHandler", Null).RunMethodJO("getSessionIdManager", Null).RunMethodJO("getSessionHouseKeeper", Null).RunMethod("setIntervalSec", Array As Object(secs))

This will be corrected in the next upcoming release 3.75 of ABMaterial.

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

 


B4A + B4J: Profile your apps ‘live’ with ABMonitor

ABMonitor2

It has been some time since I could give my donators a new goodie;), and this time it is a library/tool to profile and monitor your B4J + B4A (7.01+) apps (not limited to ABMaterial WebApps!) I’ve been needing something like this for some time for my own projects, but couldn’t find one that suited me.  This can be a very good asset to find Memory Leaks or to track which method takes up to much time.

Using a very simple API, you can track how long the code execution time is, the times hit, average time, memory usage etc for any block of code you want to investigate.  I deliberately gave you this API freedom, as profiling ALL your code (as most programming languages do) is just drowning you in to much information so you don’t see the wood for the trees anymore.

QUICK NOTE: Do not forget to set your DONATORKEY and the port in the Viewer params.txt . The same port and the IP of your Viewer have to be used in the apps you are monitoring!

How it works:
ABMonitor uses the Jamon library, which has a extremely low overhead on your code. Just by disabling it (using the SetActive method), you can actually leave it in your production apps if you want (or use B4Js conditional compiling if you want to get rid of it all together in a production app).

ABMonitor consists of two main parts:
1. The ‘live’ ABMonitor Viewer.

Instructions: Simply start the Viewer in a command prompt using: java -jar ABMonitor.jar

This Viewer shows all the stuff you are monitoring with the API. It shows e.g. how many times some part of your code was hit, how long it took, what the average time was, when it was last accessed, memory consumption etc… It is presented in a TreeView Table, so you can check every detail (depending on how deep you’ve coded your profiling).  At the bottom, the last 50 runs are presented in some line charts.

Because the IDE of B4X is ‘live’, so is the Viewer! Thanks to this unique feature of the B4X products, you can update your code and see the results in the Viewer immediately.

Little side note: This ‘live’ IDE part is a little-known/promoted feature (except within the B4X community of course, as for us this is normal), but it has a HUGE advantage over any other tool out there.  In similar market aimed tools, like Xojo for example, you have to compile and re-compile every single time you make the smallest of changes to your code (on bigger projects it takes up to 20 minutes, some users reported). In B4X, just change the code, and continue running.  Not happy with it? Change it again and see what gives. BIG production time-saver! Some of you who used to program in VB6 know what I mean…

2. The ABMonitorB4X libraries (ABMonitorB4A.jar/ABMonitorB4J.jar).

Install instructions: Copy the xml and jars (dont’t forget jamon-2.81.jar), to your B4A or B4J libraries folder. In the IDE select the ABMonitorB4X library, the RandomAccessFile library and the Network library.

This API connects your own apps with the monitor. It basically consists of a Start and a Stop method.

First, we have to make the connection with the Viewer. Thanks to Erels new Resumable Subs, doing this is a breeze:

In Main make some declarations:

Sub Process_Globals
   Public Monitor As ABMonitor

   Private port As Int = 10090 ' &amp;lt;-- Set your Viewers Port!
   Private ip As String = "127.0.0.1" ' &amp;lt;-- Set your Viewers IP!
   Private abmonitor As AsyncStreams
   Private client As Socket

   ' Useful to quickly activate/deactivate the monitoring
   Public TRACKMONITOR As Boolean = True
End Sub

Next add the following resumable sub:

Sub ConnectMonitor()
   Dim c As Socket
   c.Initialize("client")
   c.Connect(ip, port, 5000)
   Wait For Client_Connected (Successful As Boolean)
   If Successful Then
     client = c
     abmonitor.InitializePrefix(client.InputStream, False, client.OutputStream, "abmonitor")
     Log("ABMonitor connected")
     Monitor.SetActive("Template", True,abmonitor, 5)
   Else
     Log("ABMonitor disconnected")
   End If
End Sub

Sub abmonitor_Error
   Monitor.SetActive("Template", False,Null, 0)
   Log("ABMonitor disconnected")
End Sub

You can set the interval the data should be send to the Viewer, in seconds, with the last parameter in ‘Monitor.SetActive(“Template”, True,abmonitor, 5)’.  In this example, it is every 5 seconds.  Note: This does not mean it is tracked every 5 seconds! The library will continue tracking everything, but it will only send all the info over to the Viewer every 5 seconds.

Finally, Initialize the monitor and call the resumable sub when your app starts (a good place is e.g. before StartMessageLoop in a Server app):

...
Monitor.Initialize("YOURDONATORKEY")

ConnectMonitor

StartMessageLoop

Ready to do some monitoring!

As I said, there are basically only two commands: Start and Stop.

There are two ways to monitor/profile your code (you can mix the use of them):

a. Monitor some code:
You want to monitor a query, or a whole sub, … In general this is a complete block of code.

Good practice is using the class/module name as the Group parameter, and the method name as the Label, but you can put whatever you want. This will later be used in the Viewer to group stuff. (Group and Label are the first and second parameters in the calls).

Example:

Private Sub WebSocket_Connected (WebSocket1 As WebSocket)
   If Main.TRACKMONITOR Then Main.Monitor.Start("ABMPageTemplate", "WebSocket_Connected", "")

   '   ... the code you want to monitor

   If Main.TRACKMONITOR Then Main.Monitor.Stop("ABMPageTemplate", "WebSocket_Connected", "")
End Sub

Or tracking a query:

..
If Main.TRACKMONITOR Then Main.Monitor.Start("ABMPageTemplate", "MySlowQuery", "")

Dim SQL_str As String
SQL_str = "SELECT cases.CaseID, cases.CaseUserID, cases.CaseType, cases.CaseSummary FROM tCases WHERE cases.CaseStatus=1;"
Dim cases As List = DBM.SQLSelect(SQL, SQL_str, Null)

If Main.TRACKMONITOR Then Main.Monitor.Stop("ABMPageTemplate", "MySlowQuery", "")
...

b. Monitor methods which are used in multiple places, and you want to know where it was called.
You have for example a page.Refresh method, which is called in multiple places. You can use the third parameter to set the ‘caller’. In general there will only be one line of code between the start() and stop().

This caller will later be used in the Viewer to build a call tree (or stack trace)

Example:

Private Sub WebSocket_Connected (WebSocket1 As WebSocket)
     ...
     If Main.TRACKMONITOR Then Main.Monitor.Start("ABMPageTemplate", "page.Refresh", "WebSocket_Connected")
     page.Refresh
     If Main.TRACKMONITOR Then Main.Monitor.Stop("ABMPageTemplate", "page.Refresh", "WebSocket_Connected")
     ...
End Sub

public Sub ConnectPage()
   ...
   ' refresh the page
   If Main.TRACKMONITOR Then Main.Monitor.Start("ABMPageTemplate", "page.Refresh", "ConnectPage")
   page.Refresh
   If Main.TRACKMONITOR Then Main.Monitor.Stop("ABMPageTemplate", "page.Refresh", "ConnectPage")
   ...
End Sub

As you can see, you are totally free to monitor anything you want.

Call for action: if anyone out there is a guru in making B4i libraries, please PM me!  The java library code is quite simple and it shouldn’t be to hard for an experienced B4i library coder to write one which can connect to the same Viewer.  If we got this one too, then we’ve covered all major platforms in B4X!

ABMonitor is part of a multi-pack on libraries, frameworks and tools all Donators to this blog receive.

Included are currently:

ABMaterial (B4J) A very powerful framework combining a tuned Materialize CSS with the free programming tool B4J. It allows creating Web Apps that not only look great thanks to Googles Material Design, but can be programmed with the powerful free tool from Anywhere Software without having to write a single line of HTML, CSS or Javascript code!

ABExchange (B4J) It can be used to sync the ABMCalendar control with your exchange server and outlook. It can also send a mail.

ABJasperReports (B4J) his is a little library that allows you to export reports created in Jasper Reports in your B4J applications. They can be exported to pdf, html and xls. You can use parameters defined in JasperReports.

ABPDFBox (B4J) With this library you can print PDF files (either by showing a dialogbox or directly to a specified printer). Works in UI and NON-UI apps.

ABMonitor (B4J + B4A) Tool to monitor/profile your B4A (7.01+) and B4J code! Includes the libraries for both platform + the ABMonitor Viewer. See the tutorial for more info.

ABCron (B4J + B4A) A more advanced timer library that can schedule more intelligently tasks using Cron expressions. You can also set a start and end datetime. I’ve added a method RestartApplicationNONUI() that should restart you .jar file when the Cron expression is met.

ABEvaluator (B4J + B4A) Evaluate mathematical expressions. You can also write your own functions in B4J/B4A.

ABPlugin (B4J) Create Live plugins for you own apps. This means you can create plugins that can be added/removed while your app is running. Note it is a little experiment that shows the power of B4J. The ‘compile to library’ feature of B4J is very handy to create plugins.

ABZXCVBN (B4J + B4A) Realistic password strength estimation.

ABTreeTableView (B4J) Custom B4J component which combines a TreeView with a TableView.

Until next time!

Alwaysbusy

Click here to Donation and support ABMaterial


%d bloggers like this: