B4J Tutorial [ABMaterial] Custom Component Shiny Knob

Objectives
Create an ABM custom component for a shiny knob control.

b4jhowtoabmcustomknob.png


Notes
* This is the authors first ABM custom component created - thought would be helpful to share the appraoch = any hints appreciated.
* The custom component is wrapped from the knobKnob project. Many thanks to the author.
* The ABM sample project is part of the B4JHowTos and is called B4JHowToABMCustomKnob.
* Software versions used B4J v5.90, ABM 4.00 (Dragonfly).
* Not the full code is shared in the description below.
* Published on my website - which shows an advanced example with two knobs (a base for adding more knobs).

Download
B4J Source Code.
The archive does not contain the full ABM folder structure, but only what is required for this example (custom/js, custom/css).
To test the application, copy first the www folder (and only that folder) from the ABM Template folder.

Prepare
Created a folder <path>\B4JHowToABMCustomKnob
As this is a learning project, opened first the ABM Demo project in a Browser to have the documentation at hand. Esp. the customcomponent section is a must read.
From the demo project, reviewed also the CustomSlider.bas to get an understanding of the logic.

Create a new ABM project CustomKnobEx
Copy the ABM Template folder to folder <path>\B4JHowToABMCustomKnob.
Rename abmtemplate.b4j and abmtemplate.b4j.meta to CustomKnobEx.b4j and CustomKnob.b4j.meta.
Make the initial changes to the classes according ABM guidance ( see ABM Demo Project ).

Note on port used:
As the demo application is running, port 51042 is used.
Changed to port 51050 in CustomKnobEx.b4j Process_Globals. The application URL is http://localhost:51050/CustomKnobEx.

Started the application first time to check if running.

JavaScript KnobKnob Master
Download the master from GitHub.
Unpack the zip and copy
* the js to folder <path>\B4JHowToABMCustomKnob\Objects\www\js\custom\
* the css and the knob.png to <path>\B4JHowToABMCustomKnob\Objects\www\css\custom\

Note: Checked in the css file if the path to knob.png is relative to the css path, to ensure the image can be found.

Reviewed the documentation to get the code for using the knob. This is fairly easy:
B4X:
$('#elem').knobKnob({
   snap : 10,         // Snap to zero if less than this deg.
   value: 154,       // Default rotation set to 154 degrees.
   turn : function(ratio){
     // Do what you want here. Ratio moves from 0 to 1
     // relative to the knob rotation. 0 - off, 1 - max
   }
});
The knob uses a ratio between 0 - 1 which corresponds to turning the knob 0 - 360 degrees.

Befor starting to develop the application, created a HTML demo (see folder TestHTML).

Create the custom component class CustomKnob
After adding the module to the project, copied and paste the content of CustomSlider.bas, instead of using the default ABMCustomComponentTemplate.bas from the Template folder.

#Process_Globals
Defined a global var KnobTurnDegrees to be able to set the initial ratio in degrees which is assigned to the JS value var.

#Sub Initialize
Added checks to ensure ration between 0 - 1.
Set the initial value for KnobTurnDegrees.

#Sub ABMComp_Build
Added the HTML code for the selector mycustomknob:
Return $"<div id="mycustomknob"></div>"$

#Sub ABMComp_FirstRun
This is the core Sub in which the previous described JS code is created and executed.
The selector mycustomknob is executing the knobKnob function.
The initial knob turn value is taken from the global KnobTurnDegrees.
When the Knob turn (using the mouse), a B4J event is raised:
This event is defined in the, next described, class CustomKnobPage Sub custKnob_Changed(Params As Map). The parameter for this sub, is a map holding the key:value pair ratio:ratiovalue.
B4X:
   Dim script As String = $"//Init the custom knob
     $('#mycustomknob').knobKnob({
       snap : 10,    
       value: ${KnobTurnDegrees},
       turn : function(ratio){
        b4j_raiseEvent('${ABMComp.ID}_changed', {'ratio':ratio});
       }
     }); "$
   InternalPage.ws.Eval(script, Array As Object(ABMComp.ID))
The Subs ABMComp_Refresh and ABMComp_CleanUp do not have any action.

Create the page CustomKnobPage
This page uses the CustomKnob.

#Sub BuildPage
Set the page properties, define the page grid layout and add extra JS files.
The grid layout rows and cells - it is helpfull to define a comment per row with the cell size and content.
B4X:
'R1 1x6 = [Label Volume]
page.AddRows(1,True,"").AddCellsOS(1,0,0,0,6,6,6,"")
'R2 1x6 = [CustomKnob Volume]
page.AddRows(1,True,"").AddCellsOS(1,0,0,0,6,6,6,"")
'R3 1x6 = [Label VolumeValue]
page.AddRows(1,True,"").AddCellsOS(1,0,0,0,6,6,6,"")
'IMPORTANT to load the complete grid AND before you start adding components
page.BuildGrid

' Extra JS and CSS files
These are the files previous copied to the custom folder.
B4X:
page.AddExtraJavaScriptFile("custom/knobKnob.jquery.js")
page.AddExtraJavaScriptFile("custom/transform.js")
page.AddExtraJavaScriptFile("custom/wNumb.js")
page.AddExtraCSSFile("custom/knobKnob.css")

#Sub ConnectPage()
The components for the previous created rows & cells are added.
B4X:
' R1
Dim labelVolume As ABMLabel
labelVolume.Initialize(page,"labelVolume","Volume", ABM.SIZE_PARAGRAPH, True, "bluegrey")
page.Cell(1,1).AddComponent(labelVolume)
page.Cell(1,1).SetFixedHeight(50, False)

' R2
' The custom knob is added with an initial ratio of 4. Note the ID of the custom component as being used for events (see Sub custKnob_Changed) .
Dim custKnob As CustomKnob
custKnob.Initialize(page, "custKnob", 0.4)
page.Cell(2,1).AddComponent(custKnob.ABMComp)

' R3
'Define the volume value label as global as being updated when turning the knob.
'Private labelVolumeValue As ABMLabel
labelVolumeValue.Initialize(page,"labelVolumeValue","0", ABM.SIZE_PARAGRAPH, True, "aligncenter")
page.Cell(3,1).AddComponent(labelVolumeValue)
page.Cell(3,1).SetFixedHeight(50, False)

#Sub custKnob_Changed
'Handle changes of the custom knob value which is raised by the b4j_raiseEvent('${ABMComp.ID}_changed', {'ratio':ratio}); defined in the JS code in CustomKnob class Sub ABMComp_FirstRun.
B4X:
'Update the label Volume Value (0 - 100)
Sub custKnob_Changed(Params As Map)
   If Not(Params.IsInitialized) Then Return
   labelVolumeValue.Text = Round(Params.Get("ratio") * 100)
End Sub

Amend main Class CustomKnobEx.b4j
Set the port and add the CustomKnobPage.
B4X:
#Sub Process_Globals
   Dim port As Int = 51050

#Sub AppStart
   Dim myPage As CustomKnobPage
   myPage.Initialize
   myApp.AddPage(myPage.page)

Cleanup
Removed from the CustomKnobEx, the modules DBM, ABMTemplateAlternative and the jSQL library.

Run
Run the application, access via browser http://localhost:51050/CustomKnobEx and turn the knob using the mouse (point at the black dot and turn).
 
Last edited:
Top