B4J Tutorial [ABMaterial] For Dummies (beginner lessons)

Discussion in 'B4J Tutorials' started by Harris, Jan 16, 2018.

  1. Harris

    Harris Well-Known Member Licensed User

    ABMaterial For Dummies

    This has often been asked for - so it is high time to produce a simple set of guidelines to help get you across the short learning curve. Take your time and see what you have been missing in simplifying web app creation with ABMaterial!

    It only serves to muddy the waters of a clean flow.

    PLEASE - Post any questions / comments in the B4J Questions forum (title - ABM for dummies).

    Lesson 1 - Master Template Project (covers Grids, BuildPage and ConnectPage). Required! - Contains all files for ABMaterial projects
    Lesson 1a - More on ABM Grids ( Rows and Cells )

    Lesson 2 - Simple ABMLogin with Public / Private Pages

    Lesson 3 - MySQL and CRUD / ABMTables
    Lesson 3a - More about ABMTables

    Lesson 4 - Working With Containers

    Lesson 5 - ABMGenerator
    Lesson 5a - Report Setup

    Lesson 6 - Sample Next Reports

    Lesson 7 - ABMGenerator revisited (includes ABMTable)

    Lesson 8 - Deployment Problems (Local Development vs Server Differences)

    Lesson 9 - Tips and Tricks

    On the surface, ABMaterial appears to be a complex framework - which in fact, it is. However, through the series of specific topics (new posts), we will expose how easy it is to use - and construct beautiful / functional web apps.

    The first post in this series (ok, the second) will have a complete project based on @alwaysbusy (empty) Template project included in each version he releases. It shall use ABM version 4.03 which is available to everyone. This well documented example will expose the Template page (populated with controls), a simple About page and the ABMShared code module containing the navigation bar and component themes. This is generally enough to get a newcomer started - since it actually does something!

    We encourage others who are proficient with ABM to contribute well documented examples to this tutorial. Usually, this will be in the form of a stand alone "page" that can be added to the project, and any support methods needed which should go into the ABMShared code module.
    For the pupil, he/she will download your .bas and add it to the project, create a menu item in the nav bar, add requirements in Main to initialize and any methods for ABMShared.

    Many lessons (new posts) will be added with simple sections - as not to over-complicate matters.

    Here is a small sample of what to expect:
    public Sub ConnectPage()
    ' connecting the navigation bar - now that we have built one above...
        ' examine the code in ABMShared to see how this works...
    ' Let's add some components to the page - labels (with text), buttons and other controls...
        ' page.Cell(1,1).AddComponent(ABMShared.BuildLabel - here we add a label component to Row 1 / Cell 1, using a single statement
        ' and a method found in ABMShared Code Module.
        ' page.Cell(1,1).AddComponent(ABMShared.BuildLabel(page, "basic1",  - each component MUST have a Unique ID ("basic1" in this case)
        ' Notice that the text is right-justified...  How???  Using a theme - "lbltheme1"
        ' I will let you discover what the other parameters are for this method...
        page.Cell(1,1).AddComponent(ABMShared.BuildLabel(page"basic1",   "See the faint green dot above in title bar?  This means your websocket is connected! {BR} All of this page's code is explained in the source file! {BR}{NBSP}",  ABM.SIZE_H4, "lbltheme1"0"25px"))

    ' Here we add another component in Row 2, Cell 1 - It is essentially a label with block quote
        page.Cell(2,1).AddComponent(ABMShared.BuildParagraphBQWithZDepth(page"basic13",   "Hi There!  I am in Row 2 of this page!   {BR}{BR} I Have 'Flow Text' Applied. What does that mean? Resize this browser window and watch THIS text size shrink and grow accordingly!" ))

    ' Lets do something different in Row 3...

    The second post will have a link to download the base project - since it is much too large to attach here.


    Attached Files:

    Last edited: Sep 10, 2018
  2. Harris

    Harris Well-Known Member Licensed User

    Master Template Project.

    Lesson 1

    Download the linked project and unzip to a folder of your choosing anywhere on your drive.
    You MUST be using ABMaterial 4.03 and B4J 5.90 (minimum).

    In the Template folder, double click on the Template.b4j file to open the project in B4J.
    Compile the app.

    In your web browser address bar, go to:

    Read the attached ABMaterial For Dummies.pdf

    Study the ABMPageTemplate.bas which is commented. This is the template page from the menu selection.

    All other files in this project make up a foundation of any typical ABMaterial application.
    They shall be examined in greater detail in future sessions.

    download links....

    (above sites provided by yours truly - @alwaysbusy )

    PLEASE - Post any questions / comments in the B4J Questions forum (title - ABM for dummies).

    Thank you.

    Attached Files:

    Last edited: Aug 31, 2018
  3. Harris

    Harris Well-Known Member Licensed User

    Lesson 1a

    Because the concept of the Grid is so important, let us review the brief introduction from Lesson 1.

    I hope everyone caught this error in my comments from the template page - Sorry!

    ' Now the cells part of adding rows... - .AddCellsOSMP( 1,0,0,0,12,12,12, 0, 0, 0, 0,"")
    The above line is correct. I was explaining how to use AddCellsOSMP properly.

    ' AddCellsOS( 1, - add one cell to this row...
    This line is incorrect. I forgot to add the MP to AddCellsOS!
    The actual compiled code is correct thou...

    There are many ways to add rows and cells.
    They all do the same thing essentially, with additional control for various types.

    Add Rows
    These are the current methods to add rows to a grid:
    page.AddRow( Adds 1 row with cells of equal size (possible values: 1,2,3,4,6,12) , center in page, row theme, cell theme )
    page.AddRows( Adds 'x' rows, center in page, row theme )
    page.AddRowsM( Same as above with top and bottom margin )
    page.AddRowsM2( Same as above with left and right margin )
    page.AddRowsMV( top and bottom margins with visibility set for desired device - ABM.VISIBILITY_ALL - implies all devices)
    page.AddRowsMV2( all margins with visibility set )
    page.AddRowsV( Same as AddRows with visibility set )

    If you want (or need) the most flexibility when creating rows, always use page.AddRowsMV2().
    This will allow you to set any property (number of rows, all margins, visibility) - and then tweek it later if need be.

    Note: The link below explains the importance of understanding the proper use of AddRows (AddRowsM in this case...). @alwaysbusy does an excellent job of helping us understand what happens, when and why.


    Add Cells
    page.AddRows().AddCells12( number of cells , theme )
    page.AddRows().AddCells12MP( #, margins (top, bottom) / padding (left/right) and themes )
    page.AddRows().AddCells12MPV( Same as above with visibility )
    page.AddRows().AddCells12V(#, visibility and themes )
    Note: Number of cells example for above: Creating 3 x 12 Cells will 'wrap' them to the next line!

    page.AddRows().AddCellsOS( number of cells, device offset (s,m,l), size (s,m,l), theme )
    page.AddRows().AddCellsOSMP( number of cells, margins (top, bottom), padding (left, right), theme )
    page.AddRows().AddCellsOSMPV( Same as above with visibility set )
    page.AddRows().AddCellsOSV( Same as AddCellsOS with visibility set )

    If you want (or need) the most flexibility when creating cells, always use AddCellsOSMPV().
    This will allow you to set any property (number of cells, all offsets, all margins, visibility) - and then tweek it later if need be.

    You really can't see nor clearly understand all these effects until you run them in browsers on different device sizes.
    Try and adjust your desktop browser window width to emulate these effects.

    Something new about adding rows/ cells...

    Adding Row/Cell Components - .Content.CellR(0,1)
    CellR() stands for row Relative - to the current row...

    In the example below, we add components to a row (and cell) NOT by row number, but by how far to move down the grid relative to the current row.

    Let's assume the current row is Row 1 (start/top of grid).

    The next line of code states the component to be placed in current row + 0 ( ie. row 1 / cell 1).
    ABMGentiZonesModal.Content.CellR(0,1).AddComponent(ABMShared.BuildParagraphBQWithZDepth(page,"par1"," Note: Vehicle Types.") )

    This, the next line of code states to place component in current row + 1 (ie. row 2 / cell 1).

    This, the next line of code states to place component in current row + 0 (ie. row 2 / cell 2).

    This, the next line of code states to place component in current row + 0 (ie. row 2 / cell 3).

    This functionality may prove useful at times....


    The above page links describes themes and grids. By now, I hope you have a good understanding of how to apply both.
    AB also has a Grid Builder to make this process extra simple.
    This excellent tool lets you build grids and visualize how each row / cell in the entire grid will look on various devices. How cool is that!

    Last edited: May 30, 2018
  4. Harris

    Harris Well-Known Member Licensed User

    Lesson 2 - Simple ABMLogin with Public / Private Pages

    Simple because we are not using a database table (this time) to validate username and password.

    Most "membership only" websites begin with a public access page(s). Here they introduce prospects to what they have to offer before clients "sign-up".
    Once registered, users will then Login to the private pages with a username and password.

    The key to determining if login is required is by setting a NeedsAuthorization variable.

    On Public pages, NeedsAuthorization = False.
    On Private Pages, NeedsAuthorization = True

    During a successful login, a session variable (cookie) is set:
    Page.ws.Session.SetAttribute("IsAuthorized", "true" )
    ' ( Name, Value )

    ' Each page looks at this attribute to determine it's state:
        If ABMShared.NeedsAuthorization Then   ' Does this page need Authorization?
            If session.GetAttribute2("IsAuthorized""") = "" Then   '  Check for Authorization status.  If "" then direct user to Login process.
                ABMShared.NavigateToPage(ws, ABMPageId, "../")
    End If
    End If
    You will find this code block in the WebSocket_Connected of each page.

    This lesson includes two new files: HomePage.bas and ABMLoginHandler.bas. Over-write all files in the project since some other files have new / changed content.
    The home page is your public "store front" and the login handler will process a user to gain access to the private pages.

    The Home Page also provides new navigation menu options. These are only visible when viewing the public pages.

    public Sub BuildPage() ' HomePage.bas
    ' ...............
        ' builds the public nav bar as a top menu items
        ABMShared.BuildNavigationBarextra(page,  "Home","../images/logo.png""Home""Home""Home")
    ' ...............  
        ' builds the login sheets for user login process

    End Sub

    Sub ConnectPage()
    ' ConnectNavigationBar2 is purposely built for public pages... It does not require a login to view
        ABMShared.ConnectNavigationBar2(page,  "Home""Home""Home",  Not(ws.Session.GetAttribute2("IsAuthorized""") = ""))
    End Sub

    '**  Home Page  ************************************************
    ' handle the login and cancel buttons from the login sheet.
    Sub loginbtn_Clicked(Target As String)
    End Sub

    Sub logincancelbtn_Clicked(Target As String)
    End Sub


    ' ABMShared module...
    Sub BuildNavigationBarextra(page As ABMPage, Title As String, logo As String, ActiveTopReturnName As String, ActiveSideReturnName As String, ActiveSideSubReturnName As String)

    ' ...............

    ' Init the top bar navigation for public pages - use a new theme to style it....
        page.NavigationBar.Initialize(page"nav1", ABM.SIDEBAR_AUTO, Title, TrueTrue33054, sbtopimg, ABM.COLLAPSE_ACCORDION, "nav1theme")

    ' ...............  
    End Sub
    ' ABMLoginHandler.bas - The Default User Name and Password...

    ' username password
    If (logininp1.Text = "dumb") And (logininp2.Text = "dumber") Then ' username and password for dummies after all...
    ' do your thing
    end if

    Included are the complete set files including project and .bas files.
    Replace all the existing files
    If you are familiar with B4J, then this lesson should be easy.

    The login introduces a ModalSheet. The creation structure is just like a page with rows and cells.
    The sheet is first built and then called using:
    page.ShowModalSheet("login") - from the HomePage.

    The sheet is closed using:

    You should be able to read / follow the code and understand the logic.
    A little explanation can go a long way. If you get stuck, ask a question in the B4J Questions section.


    Attached Files:

    Last edited: Jan 23, 2018
  5. Harris

    Harris Well-Known Member Licensed User

    Lesson 3 - MySQL and CRUD / ABMTables

    Sure, you could use Wix, Go-Daddy or any one of the other so called web-site development systems to create your picture based site (in about an hour...).
    However, these systems don't support a database that you can add and manage. I guess that's why they are site development and not web-app systems.
    Database support is what really makes a web-app. You can use any database (MSSQL, Oracle, postgres, etc) since JDBC drivers exist for all of them.

    This lesson provides a basic, yet interesting example of how to work with database tables and records in your ABM projects.

    The attached zip has everything (code) included.
    You will need a WAMP (Windows version) to continue with this lesson (see below for a nice light one...).
    Also, import the attached users.sql (users.zip) into your database - using your favorite DB manager. I use phpmyadmin.

    Adding MySQL Support Drivers

    In the Main of our project, we must add the driver for MySQL. This driver file will be placed in our Libraries folder.
    The required jar is too large to attach to this post.
    ' #AdditionalJar: mysql-connector-java-5.1.37-bin
    ' #AdditionalJar: mysql-connector-java-5.1.44-bin ' latest jar file...

    Now you must init this driver.
    ' you will need YOUR database name, user name (login), password and pool size

    ' DB Name username password pool size
    ' DBM.InitializeMySQL("jdbc:mysql://localhost/ harris ?characterEncoding=utf8", "harris", "b12xw3455", 100 )
    ' or...
    ' DBM.InitializeMySQL("jdbc:mysql://localhost/database_name?characterEncoding=utf8", "username", "password", 100)

    The Main also explains how the apps pages are inited and added.

    Login - Uses a database lookup.

    In ABMShared, UsingDB is set to True. This forces the login system to use a table for user authorization.
    Public UsingDB As Boolean = True ' Using a table to check username and password
    Check out the code in ABMLoginHandler - the HandleLogin method.

    UsersPage.bas - The CRUD page

    The table system (ABMTable) in ABMaterial is second to none.
    Study the ConnectPage, LoadUsers and tblUsers_Clicked methods carefully.
    These explain how to create ABMTables, populate and work with them.
    Also, @alwaysbusy created a CRUD code generator. It can easily create the crud code for you - but you should know how this code works just the same.

    Web server link (if you don't have one already). I use this for my day to day development on my local pc.
    I use an industrial LAMP on my Linux remote production servers.


    Attached Files:

    Last edited: Apr 10, 2018
  6. Harris

    Harris Well-Known Member Licensed User

    Lesson 3a - More about ABMTables

    Since we had so much fun in Lesson 3 learning about ABMTables, I have added more content along the same lines....
    In this section we will add ABMLabel, ABMImage, and ABMSwitch to our ABMTable - all quite easily!

    Attached are 2 files:
    UsersPage.zip - the .bas file reworked for this lesson...
    dumb.zip - Place the folder in this zip in your: www/template/images folder. (result: "www/template/images/dumb")

    Here is a snap shot of the new ABM Users Table... The Edit (modalSheet) has changed as well...
    Read comments in the .bas file for instructions - as per usual...



    Attached Files:

    Last edited: May 30, 2018
  7. Harris

    Harris Well-Known Member Licensed User

    Lesson 4 - Working With Containers

    Question: What are containers?
    Answer: A place to put your stuff...

    Or more specifically, "another" place to put your stuff (components) - aside from the page.

    Containers are special and powerful components useful in creating wonderful apps.
    In this example, we will use 1/3 of the page to hold a container and the rest to show container actions.

    I shall supply the .bas file. It is now up to you to:
    1) add it to the project,
    2) init it in Main,
    3) add the page name to the navigation menu in ABMShared (or replace an existing item - like
    page.NavigationBar.AddSideBarSubItem("Config", "Company", "Test Containers", "mdi-action-home", "../ContPage") )

    More on this subject by @alwaysbusy

    Good luck and enjoy this simple lesson.
    When the lessons are completed, perhaps you will create the advanced - "ABMaterial for Experts" !!!

    Attached Files:

  8. Harris

    Harris Well-Known Member Licensed User

    Lesson 5 - ABMGenerator (Part A)

    This handy tool was mentioned previously and really deserves a closer look.
    In part A of this section (one of many), we shall look at what it (simply) takes to generate modal sheet code for CRUD.
    I think that once you see the power of this, you won't bother writing these by hand any longer (or copy/paste as I have done previously).

    Back when ABMaterial was first introduced, I asked @alwaysbusy if it was possible to create a code generator that would reduce the drudgery of writing ABMTables and Modal Sheets code - since each time the result was practically identical. Initially, he had some reservations about this yet 2 days later - ABMGenerator appeared!
    The result was much more than I could ever image possible. I swear he has a team of clever elves coding somewhere, like in Erel's basement - next room over from Erel's group. :) What do you feed elf coders? (Bits and Bites!).

    The attached About.bas contains the generator sections. For now, all we shall do is let it generate the code (lot's of it) - then view it.
    Each line is documented as to its' function in the generation process.

    Read (study) the output result. Be amazed (as I was) of the code design. Only a true professional could think of creating such well structured code.
    Look how updates and inserts are handled. This sure beats (by 1000 fold) the ultimate mess I would have written left to my own sparse knowledge of proper design.
    I have personally learned so much from ABM code examples - from his supplied projects. Now you can too since you will have a good idea of what you are looking at.

    In the next segment of this multi-part lesson, we shall use the generated output on a table I shall supply. We will also fill in some of the "blanks" of this result - since a generator can't do everything - but darn near! Also, we shall build the required ABMTable since the generator doesn't do this currently (easy enough for us however). We will look at a template for the "LoadTable" method - required and similar in each case where you will build new pages containing new (different) DB tables.

    The end result of our efforts in this series will introduce us to Next Reports - and how we shall use what we have built to drive / configure a simple reporting system (also based on the previous use of containers lesson).

    Again, ABM is essentially just your GUI. Let's put it to work directing B4J code, ABM complete components / helpers and other important web app things.

    We take things slowly around here - like Bill Murray in the movie "What About Bob" - baby steps....


    Attached Files:

    Last edited: Feb 3, 2018
    clarionero, inakigarm, Anser and 7 others like this.
  9. Harris

    Harris Well-Known Member Licensed User

    Lesson 5a - Report Setup (Part B)

    In this session we shall put our generated code to good use. We shall make the Report Setup form.
    Invariably, we may find our initial design (that we asked the generator to create) was slightly off the mark... No problem, we can fix it.
    The point was - it created a bunch of clean code we didn't have to write.

    Here is a list of things we must do / provide with the generated code:
    1) Create your ABMTable construct. We covered this previously.
    2) Provide the table name and where clause field name in SELECT, INSERT and UPDATE statements. I've shown where these go...
    3) Correct the modal sheet footer in the ABMGenBuildreport (or whatever the name is)... Add the button Text Name (Save and Cancel) and remove the transparent theme.
    4) Add margins for each row in the build sheet.
    5) Add titles to the Switch Components (when required).
    6) Re-order our input fields in our sheet.

    Note: All above has been corrected in the attached...

    Report Setup
    The Report Setup form is a simple helper that defines what reports our application provides and what parameters are required to run each.
    We can add new reports as they become available - and update our application with little effort.

    Reports Page
    This is where we will put our setup to use. Each report we choose may have various and different parameters shown to allow proper configuration.
    This section also introduces the use of ABMSideBar. This cool component keeps our main page clean while allowing our configuration container to slide in/out when required.

    Unzip the files to your template project (overwrite where asked).
    Add the two new .bas files to your project (reportPage, setup_paramPage)

    Import the report1.sql into your db. This is required to drive our reporting system.

    Add the following into Main, where shown...

    Dim reportp As reportPage

    Dim reportset As setup_paramPage

    '****** End Init pages **********************************************
    ' add each of the pages to the app
        '***** Start Add pages ***********************************************

    Under Configure App - see Setup Reports

    Under Applications - see Reports Page

    In the next lesson of this series, we shall add some actual reports. This requires the Next Reports framework.
    I will supply some report files based on our data for the next lesson.
    Creating reports can be entertaining - when you don't have to fight with a reporting framework. Each has it's quirks and features.
    I have found that the more you pre-process your reporting data - the easier the process is.
    We shall create temp tables for our data - add blobs to support image printing and other fun stuff...

    If you are not familiar with NR, I suggest you download it and study how it works.
    You will need the engine files for the next lesson. Like B4J - it's all FREE...

    Get the NR designer...

    And the NR engine...

    We won't need the server - however it is nice to know there is one with NR (just like the expensive big boys).

    Finally, I shall have to assume that the lack of questions in the B4J Questions form concerning ABM for Dummies means that I am accomplishing the task of keeping this simple - for all us dummies.... As you can see - this stuff isn't difficult - when shown how easy...


    Attached Files:

    Last edited: May 30, 2018
  10. Gabino A. de la Gala

    Gabino A. de la Gala Active Member Licensed User

    The truth is that everything worked the first time.
    A really good job. Thank you very much.

    The only thing that did not work the first time was the connection to the MySql database. Finally it was only to change the default port that came in the 3307 portable application by the 3306 and it worked.

    I am anxious waiting for the next lesson.
    Thanks again.
    clarionero likes this.
  11. Mashiane

    Mashiane Expert Licensed User

    Can't wait... thanks man, this is good stuff here...;)
    clarionero and joulongleu like this.
  12. Harris

    Harris Well-Known Member Licensed User

    Lesson 6 - Sample Next Reports

    This lesson requires Next Report jar files as defined in the previous lesson.
    It also requires the jNxtReportsB4J library.

    This example has very little to do with ABMaterial. The ABMPDFViewer is used to show the NR pdf generated reports.
    We shall also show reports, in a new tab, when generated using the NR html output format.

    In the Main.bas, include the following lines in the project attributes section...

    ' Don't forget to add jNxtReportsB4J in the B4J IDE Libraries tab
    '    Download these jar files to the B4J Libraries folder
    '    All these jar files are available from http://www.next-reports.com/download.html
    Supplied files:
    Replace the reportPage.bas with this new file attached.
    Place the pics folder into the template\images folder. (result -> template\images\pics )
    Place the reports folder into the the Objects folder. (result -> Objects\reports)

    Import the sql files into your database. Note: replace existing report1 with supplied file. Add insptype.

    The reports folder contains the NR report templates. These were created with Next Reports 9.1
    You can open these with your installed copy of Next Reports.

    The reportsPage.bas is well commented. Make sure you supply the correct url for the jdbc database definition - since the supplied code won't work in your environment.
    Next Reports will not generate a report when in Debug mode (use release mode).

    Good luck and remember to post comments or questions in the B4J Questions forum.

    Edit: The users.zip in lesson 3 has been updated to include images (pics) in case your example does not include it. This may happen if you did not follow these lessons from the beginning (lesson 1).


    Attached Files:

    Last edited: Apr 12, 2018
  13. Mashiane

    Mashiane Expert Licensed User

    After careful thoughts, I decided to work on Part 1 of this very big task. With this video, before I delve into the code and what to change, we look at what ABMaterial is, its structure (blank template), the Demo project and various other elements that we use to create our web apps. I hope you will enjoy the video as much as I enjoyed creating it. Ta!

    Watch in HD.

  14. Mashiane

    Mashiane Expert Licensed User

    Templating & Your Next ABM WebAPP

    Hi there...

    This is a continuation about the template video I added here..

    I have been thinking about the next section of the 'Template' and would really like to do my contributions here a lot of justice. In my post, above, I back-tracked a little to give an overview of everything this seeks to address, as a result, rather took a slow approach. Reason being, as a creator, the question of what you want to achieve with ABM and the understanding of how everything fits in together, becomes extremely crucial. So my two cents resulted in this diagram below.

    NB: Click the image to expand it and see it nicely...


    Before one writes even a single line of code, a pure understanding of how everything ABM is knitted together is necessary. I believe you are here because you are interested and also want to develop beautiful things with ABM. You have made a very good decision. This is a very beautiful framework and above I just did an overview of how I understand it, I'm still learning like everyone else and still make mistakes.

    An ABM project is made up of components that can be categorized into Input Controls (user has to enter/select something), Buttons, Media (display something, play music), Layouts (contain other components), Navigation (move around the page), Events related components e.g. calendar, planner etc, Reporting & Other things like the XTR (translating your app), Conzole (debug your app), ABMGenerator (Lesson 5a above) and Security (captchas, social-auth) etc.

    I have tried to remember as much as I can referencing the Demo ABM app also. Please if there is anything I have left out, kindly shout!

    All of the components indicated above will help one using the ABMPageTemplate to create beautiful UXs for their apps.

    When all the components that you want to use for your app are built into each of the pages you will have, these compile into a WebApp that you can then deploy to VPS. There are some developments towards publishing on GCE (Google Cloud Environment).

    In summary, the components feed into your ABM WebApp, you code each component you need to sit inside any of the layouts, especially the ABMPage (ABMPageTemplate class), these using B4J are compiled to into a WebApp, they you deploy this to a VPS.

    In my next post and hopefully last post about the template, I will conclude about the three most important 'blank template' classes that you need to be up and running with your ABM WebApp.

    I hope you found this informative just like I also found it.

    PS: This diagram is created using a custom component. See here
    Last edited: Apr 18, 2018
    clarionero, joulongleu and microbox like this.
  15. Mashiane

    Mashiane Expert Licensed User

    Your ABM WebApp Design: Part 1

    Now that you have an idea of what your next ABM will contain as per components discussed above. You need to look at the design, the look and feel and how everything will work together to make your perfect app. This answers the question of where the components will be placed?

    The starting point for that is the ABMGrid. The grid helps you place the components/controls you want on your containers, i.e. ABMPage, ABMContainer, ABMSmartWizard, ABMTabs etc etc.

    Let's just do a quick refresher based on Lesson 1A above.

    NB: Most of the ABM component needs to sit in a row/cell co-ordinate in a Grid.

    The ABMGridDesigner is a tool to help you create the grid that will host your components. Basically it helps you create something like Figure 1 below without typing the code. AB started the grid concept in 2016 and it has matured a great deal after that.

    Figure 1


    1. You need to add rows to the grid. Each row should have a cell or a couple of cells. The various methods to add rows as discussed by Harris, are AddRow, AddRows, AddRowsM etc. Each cell is added using any of the methods like AddCells12, AddCellOS, AddCellOSMP etc.

    2. You can add 1 row at a time or add multiple rows at the same time. The cells added will apply on all the rows being added. In the above Figure, 1 row was added to demonstrate each of the various methods of adding the rows.

    3. Each row as 12 cells / columns. This is depicted by the faint gray lines in each row.

    Let's explain Figure 1 above.

    I have created a grid with 12 rows and 12 columns/cells across. The sizes e.g. 111,999,222 indicate the span of each cell/column. For example, AddCells12(1,"") will add 1 single cell in that row spanning 12 spaces.

    3.1 AddCellOS(2,0,0,0,6,6,6,"") - adds 2 cells in that row spanning 6 spaces. This can be also done with .AddCellOS(1,0,0,0,6,6,6,"").AddCellOS(1,0,0,0,6,6,6,"")
    3.2 The example with cells 1,1,1:9,9,9:2:2:2 adds 1 cell, 9 cells and 2 cells as depicted above respectively.

    4. In all the rows in Figure 2 except the first row we have 2 cells/columns. The first row will be referenced with...

    The 2nd row

    The 7th row has 3 cells, thus to add anything in each of them one will use...

    NB: Each time you execute an AddRow method, ABM increments the existing row counters with the number of rows to add. In the Figure above, AddRows(1...) was used for all the row:cell system. The ABM framework then does the magic of referencing the RCs for you and allocates the correct RC co-ordinates for you.

    What about the MP (margins and padding) in the methods?

    I have left that out because this is basically related to something called the CSS Box model. Let me explain. In the video, I indicated that ABM generates HTML and CSS code for your WebApp, with you not having to write that. That was AB's good intentions.

    Each element that you see on your ABM webapp is an HTML element, meaning that it can be considered as a box. In CSS terms, when talking about designs and layouts, the term, "box model" is used. This box model wraps around it every HTML element. It's made up of margins, padding, border, and the actual content. So as an example, an ABMInput control will have padding, a border and a margin around it etc etc.

    Explanation of the different parts:

    Content - The content of the box, where text and images appear
    Padding - Clears an area around the content. The padding is transparent
    Border - A border that goes around the padding and content
    Margin - Clears an area outside the border. The margin is transparent


    As noted, this padding and margins can be set for the rows and cells by specifying the M and P variables when adding the rows and columns. That should cover the MPs.

    V - Visibility

    Each component/row/cell that you add can have a visibility set. This visibility indicates how your HTML element should be displayed within the various devices that you are designing for. Remember, when working with the grid-designer, you are designing for small, medium and large devices at the same time. The ABMWebAppViewer shows the output of your app perfectly when you run it. You can then tweak it.

    The enumeration for the visibility is..

    This then allows flexible design for your apps as you can hide/show components depending on your device.

    As you have picked up, there are Offset properties when adding cells to a row. We will touch on that in Part 2 of this intro.

  16. Mashiane

    Mashiane Expert Licensed User

    Your ABM WebApp Design: Part 2

    This part of the design talks about Offsets. One of the users asked the other day how he can have buttons on either side of his row? AB answered him by pointing him to offsets.

    When creating Cells in your rows, there are variables for small, medium and large devices that are usually indicated with 0,0,0 then followed by the size of each cell for each device.

    To put it simply, offsets indent / nudge to the right your cells for each of the device specifications. With ABM, you specify how much this indentation/nudging should be as it takes space.

    Figure 1


    1. In figure 1 above, as we are aware, each ABMRow has 12 imaginary ABMCells, i.e. 1R=12C.
    2. If we want to nudge/indent something, we need to offset it by the number of spaces that it needs to take. In row 2, we have used an offset of 1 (1 space to the right) and 11 spanning spaces for the cell.
    3. In row 3, same story, we nudged the cell to the right it taking 2 spaces. 2 offsets + 10 spaces = 12 cells
    4. Now the fourth row is rather interesting. We want to have a component on either side of the row. We add 1 cell spanning 2 cells and another cell spanning 2 cells with an offset of 8, making the cell complete with 12 cells.
    5. In all the rows except row 4, each row has just one cell, thus, referencing it when adding components, one would use .Cell(x,1).AddComponent. With the 4th row, its .Cell(4,1).AddComponent(c1) and .Cell(4,2).AddComponent(c2).

    This is due to the fact that we cannot use TAB to indent our layouts, so a smart way was created, Offsets.

    Please note: One last thing I would like to touch on, time allowing, before we delve on actual coding of the 'blank template' to create a new ABM project is 'Theming'. Please just bear a little while.

    PS: And this was so hard to grasp when I started ABM. It's a marvel that we have the grid-designer to help us through this, however before you even start with that, you need to have an idea of how you want your pages to look like and where your components should be placed.

    I hope so far this thread is helping you achieving that.

  17. Harris

    Harris Well-Known Member Licensed User

    Lesson 7 - ABMGenerator revisited...

    In a previous lesson we looked at the first version of the ABM Code Generator.
    I was reminded of a newer release of the generator that produces much more than ABMSheets.

    This lesson is a stand alone Template project (backup your old project or use a new folder if required).
    It will create a simple page populated with the code from the generator. The nice thing about this lesson is the creation of an ABMTable (a grid with records).
    ABMTables generally require support methods to use them effectively. The generator writes all the code for you!
    Simply modify certain sections - mostly to make it look pretty / apply themes - and enjoy all the code you DID NOT have to write!

    This example uses MySQL as the database - and a users table for the example. Rename your existing users table if you have one prior to importing this one.
    Modify the Init method in the Main.bas to your requirements.

    The "ABMPageTemplate" is well commented and includes the code from the generator - some modified slightly.


    Attached Files:

  18. Mashiane

    Mashiane Expert Licensed User

    Explaining the Template Folder - Part 1 : The ABMApplication.bas file

    This module is like the server part of the application where you specify things needed to make the app work. Its like the index.html part of your application, however you call it from the Main module. You start the webserver from this page after you have added your app pages. Adding pages and starting the server is done in the Main module. When creating a new ABM project, you need a variety of files, these are in the Template folder and you copy the contents of that folder to a new folder with the name of your app and then do some renaming across the files to make your app work.

    The Demo app does a lot of justice in terms of explaining how ABM works. The purpose here is just to explain a few things based on what I've learned so far. Any time you create a new application, there are a variety of things to consider.

    1. The port your app will run on. This port should be opened in the firewall for both incoming & outgoing traffic.
    2. If your app will use Google Maps, create a map key from google and update ABMShared with the key, the GoogleMapsAPIExtras property
    3. If your app will use Chat functionality, ensure you initialize the Chat module in Main.
    4. To link your app pages to the app, add these to your application in the Main module

    Dim myApp As ABMApplication
    Dim myjobs As jobs
    5. What backend database will you use, this needs to be opened when the AppStart, usually done in Main or when needed.
    6. To compress your images with the build in GZip, you need a Donator key
    7. To use ABM's CDN, you need a donator key
    8. The page your app will start on needs to be clearly defined, page names are case sensitive, preferably just use lower case. This creates some issues on windows vps servers where filenames are different. ABM is case sensitive with file names.
    9. ABMShared is a very valuable module where your shared code can be placed that will be available across the app.
    10. Most development of your app happens on each instance of ABMPageTemplate, there is very little to change on ABMApplication and ABMShared.
    11. Your app name should not contain spaces (recommended). Update ABMShared with the app name, the AppName property
    12. Ensure that the cache scavenger settings for refreshes and your connection pool for database connections is sufficient (see demo apps for settings)
    13. The viewer is very handy to see your app across the various devices.
    14. You can also have a TrackingID for your app for google stats purposes.
    15. You can build themes for the app in ABMShared and you can also build themes for your components in each page. ABMShared.BuildTheme is useful for that.
    16. If your app needs Sign In features, you can turn ABMShared.NeedsAuthorization to true. Ensure you use the Login functionality discussed in ABMFeedback.
    17. If you app will use the ABMFileManager, this needs to be linked to a particular folder on start. This is changeable via code though. Additional code is needed to complete the installation of this file manager, see the post here in the forum.
    18. If your pages will share the same navigation system e.g. SideBar and TopNav, you can define this structure in ABMShared using BuildNavigationBar and ConnectNavigationBar and just call these methods when building your pages
    19. You can define any component in ABMShared e.g. ABMContainer, ABMModalSheet etc, however trapping events should be done at page_level for those to work in your page.
    20. If your app will have a shared footer, you can define its structure in ABMShared and then call this on your page.
    21. If your app will use Social Media sharing, you need to have the respective apps created e.g. facebook or else have links pointing to those social pages. Yes, you can link a component to be posted to social networks.
    22. If you run in SSL environment, the method to start the server is different, its StartServerHTTP2
    23. Should there be a component (JavaScript based) component that you need that does not exist in the current package, the ABMCustomComponent exists to create such components. Knowledge of JQuery, JavaScript, CSS is necessary for such things.
    24. The default these for ABM apps is light blue, you can customize your app by building themes specific to each component that you need to enhance the look and feel of your app.
    25. Some components have more that one Initialize method to create them for your pages depending on what you need.
    26. You can have SQLite as a backend and activate multi-user on it. With the SQL library you can use both MySQL and MSSQL. The DBM module has been created by AB for database connections and manipulations. The ABMGenerator discussed above and other parts of this thread touched on this.
    27. If your images shown via ABMImage etc will be changing and thus dynamic, append DateTime.Now timestamp on them, this will ensure that the latest images are displayed in the server. If not, the images from the cache will be used.
    28. You can have different loading methods on your app and these can be shown with .Pause and .Resume methods but however are activated automatically when a page is loading.
    29. You can define HTML for your error pages in ABMErrorHandler
    30. You can leave out the app name when opening your app via a web browser by using the ABMFilter in your app.
    31. A file upload handler exists where file management is concerned, this can be tweaked to replace existing files when uploads happen.

    Method: ABMApplication.Class_Globals

    Property: InitialPage – here specify the name of the page the application will start on. This name is case sensitive and must match the class name of your page. This should be the complete path of your page e.g. frmIndex/frmIndex.html

    Method: ABMApplication.Initialize

    • Here define the apple touch icon, tile icons, fav icon to use for your application. You can also specify the Thresholds for the small and medium devices.
    • If you want the CreatedWith to be obfuscated, you can also specify this here by passing it the donation key
    • If your app will have a manifest, also add the line specifying that here.
    • You can also call the method to set the font sizes here, including the expiry times for your pages.
    • If you have a donation key, provide it where it is needed e.g. for GZip or ActivateCDN.

    This module also has BuildPage and ConnectPage, which means you can run code that will run in your “index.html” here. You can see the Demo app and other attached ABM demos about how these are defined.

    For my BuildPage method, I indicate the PageTitle, the PageHTMLName etc. For PageHTMLName you need to add the .html extension to the name.

    The ABMFeedback app has additional methods here to create a login page for the app, explore that for more details.


    PS: Should anyone notice an error, please indicate. Thanks. ;)
  19. Harris

    Harris Well-Known Member Licensed User

    Lesson 8 - Deployment Problems (Local Development vs Remote Server Differences)

    We see this issue often:

    "Everything works great on my development platform - but I get different results when I deploy it to my remote server!"

    There are four (and possibly more) reasons for this.

    1) "copymewithjar.needs" file...
    Each time you update your "myapp".JAR file on your remote server, make sure you also copy this file as well - to the same location.
    It contains the required class names that are required to execute your app properly - and will ensure they are linked (magically).

    2) The www folder:
    Make sure the js, font, and css folders are current. Every time alwaysbusy releases a new ABM version upgrade (ie. 4.15 to 4.20), certain files in these folders will change to support the (bug) fix or new features.

    3) Custom / js folder
    If you have any custom js files (from custom components you made), make sure you update the appropriate folder with the required files. Only you will know where these need to be placed.

    4) Java version. This is typically a B4J issue. Does your dev system use the same java as the server?

    These are the most common issues. If you have exposed other areas where one can get tripped up using ABM - PM me with your solutions and I shall add them to this section (easiest method to keep it all together).

    Last edited: Sep 2, 2018
    amaxco, Mashiane, inakigarm and 2 others like this.
  20. Mashiane

    Mashiane Expert Licensed User

    ABMaterial Component Events Quick Reference: For more details explore the XML document.

    Each ABM component can have one or more events. Events happen when a button is clicked, a radiogroup item is changed, a checkbox is checked etc etc. Below is a collection of events in component order for a quick reference. Each component is prefixed by the class name. When inserting the code in your page, you can replace the component class name with the actual name that you used when Initializing the component.

    Where there is a 'Target' name, this is the actual html id of the component as defined by ABM.

    In summary, if you have an action button on your page, you can copy the code related to the action button and then replace ABMActionButton with your component id e.g.

    Sub mib_Clicked(Target As String, SubTarget As String)
    End Sub
    Below are all the events that you can use for all the components.

    Sub ABMActionButton_Clicked(Target As String, SubTarget As String)
    End Sub

    Sub ABMAudioPlayer_Done()
    End Sub

    Sub ABMButton_Clicked(Target As String)
    End Sub

    Sub ABMButton_MenuClicked(Target As String, returnName As String)
    End Sub

    Sub ABMCalendar_DayClicked(date as String)
    End Sub

    Sub ABMCalendar_EventClicked(eventId as String)
    End Sub

    Sub ABMCalendar_EventEndChanged(eventId as String, NewEnd as String)
    End Sub

    Sub ABMCalendar_EventStartChanged(params as Map)
    End Sub

    Sub ABMCalendar_FetchData(dateStart as String, dateEnd as String)
    End Sub

    Sub ABMCanvas_CanvasDown(x as int, y as int)
    End Sub

    Sub ABMCanvas_CanvasUp(x as int, y as int)
    End Sub

    Sub ABMCanvas_ObjectClicked(objectId as String)
    End Sub

    Sub ABMCanvas_ObjectDown(objectId as String)
    End Sub

    Sub ABMCanvas_ObjectUp(objectId as String)
    End Sub

    Sub ABMCard_LinkClicked(Card as String, Action as String)
    End Sub

    Sub ABMCheckbox_Clicked(Target As String)
    End Sub

    Sub ABMChip_Clicked(Target As String)
    End Sub

    Sub ABMChip_Closed(Target As String)
    End Sub

    Sub ABMCombo_Changed(value As String)
    End Sub

    Sub ABMCombo_Clicked(itemId as String)
    End Sub

    Sub ABMCombo_GotFocus()
    End Sub

    Sub ABMCombo_LostFocus()
    End Sub

    Sub ABMComposer_Add(blockID as String)
    End Sub

    Sub ABMComposer_Edit(blockID as String)
    End Sub

    Sub ABMComposer_Remove(blockID as String)
    End Sub

    Sub ABMContainer_AnimationFinished(Target as String, lastAnimation As String)
    End Sub

    Sub ABMContainer_NextContent(TriggerComponent As String)
    End Sub

    Sub ABMCustomComponent_Build(internalPage as ABMPage, internalID as Stringas String)
    End Sub

    Sub ABMCustomComponent_CleanUp(internalPage as ABMPage, internalID as String)
    End Sub

    Sub ABMCustomComponent_FirstRun(internalPage as ABMPage, internalID as String)
    End Sub

    Sub ABMCustomComponent_Refresh(internalPage as ABMPage, internalID as String)
    End Sub

    Sub ABMDateTimePicker_Changed(Target as String, dateMilliseconds as String)
    End Sub

    Sub ABMDateTimePicker_ChangedISO(Target as String, dateISO as String)
    End Sub

    Sub ABMDateTimePicker_ChangedWeek(Target as String, WeekString as String)
    End Sub

    Sub ABMDateTimePicker_ChangedWeekISO(Target as String, WeekString as String)
    End Sub

    Sub ABMDateTimeScroller_Changed(dateMilliseconds as String)
    End Sub

    Sub ABMDateTimeScroller_ChangedISO(dateISO as String)
    End Sub

    Sub ABMEditor_Loaded(Target as String)
    End Sub

    Sub ABMFileInput_Changed(value As String)
    End Sub

    Sub ABMFileManager_Downloaded(jobcode as String)
    End Sub

    Sub ABMFileManager_FolderChange(folder As String)
    End Sub

    Sub ABMFileManager_RequestForCopyPaste(fromFolder as String, jsonList as String)
    End Sub

    Sub ABMFileManager_RequestForCreateFolder()
    End Sub

    Sub ABMFileManager_RequestForCutPaste(fromFolder as String, jsonList as String)
    End Sub

    Sub ABMFileManager_RequestForDelete(jsonList as String)
    End Sub

    Sub ABMFileManager_RequestForDownload(jsonList as String)
    End Sub

    Sub ABMFileManager_RequestForRename(oldFileName as String)
    End Sub

    Sub ABMFileManager_UploadFailed(status As String, message as String)
    End Sub

    Sub ABMFileManager_UploadStarted()
    End Sub

    Sub ABMFileManager_Uploaded()
    End Sub

    Sub ABMGoogleMap_Clicked(Latitude as Double, Longitude as Double)
    End Sub

    Sub ABMGoogleMap_CurrentLocation(Latitude as Double, Longitude as Double)
    End Sub

    Sub ABMGoogleMap_Error(ErrorMessage as String)
    End Sub

    Sub ABMGoogleMap_GeoCodeResult(Latitude as Double, Longitude as Double)
    End Sub

    Sub ABMGoogleMap_MarkerClicked(MarkerId as String)
    End Sub

    Sub ABMGoogleMap_MarkerDragEnd(Latitude as Double, Longitude as Double)
    End Sub

    Sub ABMGoogleMap_MarkerDragStart(MarkerId as String)
    End Sub

    Sub ABMGoogleMap_Ready()
    End Sub

    Sub ABMGoogleMap_ReverseGeoCodeResult(Address as String)
    End Sub

    Sub ABMImage_Clicked(Target As String)
    End Sub

    Sub ABMInput_AutoCompleteClicked(uniqueId as String)
    End Sub

    Sub ABMInput_Changed(value As String)
    End Sub

    Sub ABMInput_EnterPressed(value As String)
    End Sub

    Sub ABMInput_GotFocus()
    End Sub

    Sub ABMInput_LostFocus()
    End Sub

    Sub ABMInput_TabPressed(target As String, value As String)
    End Sub

    Sub ABMLabel_Clicked(Target As String)
    End Sub

    Sub ABMList_Clicked(ItemId as String)
    End Sub

    Sub ABMPage_AjaxError(uniqueId as String, error as String)
    End Sub

    Sub ABMPage_AjaxResult(uniqueId as String, result as String)
    End Sub

    Sub ABMPage_Authenticated(Params As Map)
    End Sub

    Sub ABMPage_B4JSAjaxError(uniqueId as String, error as String)
    End Sub

    Sub ABMPage_B4JSAjaxResult(uniqueId as String, result as String)
    End Sub

    Sub ABMPage_B4JSInputboxResult(returnName As String, result As String)
    End Sub

    Sub ABMPage_B4JSMsgboxResult(returnName As String, result As String)
    End Sub

    Sub ABMPage_CellClicked(Target as String, RowCell as String)
    End Sub

    Sub ABMPage_DebugConsole(message As String)
    End Sub

    Sub ABMPage_DragCancelled(component as String, source as String)
    End Sub

    Sub ABMPage_DragStart(component as String, source as String)
    End Sub

    Sub ABMPage_Dropped(Params as Map)
    End Sub

    Sub ABMPage_FileUploaded(FileName as String, success as Boolean)
    End Sub

    Sub ABMPage_FirebaseAuthError(extra As String)
    End Sub

    Sub ABMPage_FirebaseAuthStateChanged(IsLoggedIn as boolean)
    End Sub

    Sub ABMPage_FirebaseStorageError(jobID As String, extra As String)
    End Sub

    Sub ABMPage_FirebaseStorageResult(jobID As String, extra As String)
    End Sub

    Sub ABMPage_InputboxResult(returnName As String, result As String)
    End Sub

    Sub ABMPage_ModalSheetDismissed(ModalSheetName as String)
    End Sub

    Sub ABMPage_ModalSheetReady(ModalSheetName as String)
    End Sub

    Sub ABMPage_MsgboxResult(returnName As String, result As String)
    End Sub

    Sub ABMPage_MultiCellFinished(Params As Map)
    End Sub

    Sub ABMPage_NativeResponse(jsonMessage As String)
    End Sub

    Sub ABMPage_NavigationbarClicked(Action As String, Value As String)
    End Sub

    Sub ABMPage_NavigationbarSearch(search as String)
    End Sub

    Sub ABMPage_NextContent(TriggerComponent As String)
    End Sub

    Sub ABMPage_ParseEvent(Params As Map)
    End Sub

    Sub ABMPage_RowClicked(Target as String, Row as String)
    End Sub

    Sub ABMPage_SignedOffSocialNetwork(Network as String, Extra as String)
    End Sub

    Sub ABMPage_SizeChanged(previous as String, current as String)
    End Sub

    Sub ABMPage_ToastClicked(ToastId As String, Action As String)
    End Sub

    Sub ABMPage_ToastDismissed(ToastId As String)
    End Sub

    Sub ABMPage_VisibilityState(State as String)
    End Sub

    Sub ABMPagination_PageChanged(OldPage as int, NewPage as int)
    End Sub

    Sub ABMPatternLock_Changed(target as String, value as String)
    End Sub

    Sub ABMPivotTable_Loaded(Success As Boolean)
    End Sub

    Sub ABMPivotTable_RendererChanged(newRenderer As String)
    End Sub

    Sub ABMPlanner_ActiveDayChanged(day As Int)
    End Sub

    Sub ABMPlanner_MenuClicked(MenuType As String, Value as String)
    End Sub

    Sub ABMPlanner_MinutesClicked(Value As String)
    End Sub

    Sub ABMPlanner_Refreshed()
    End Sub

    Sub ABMRadioGroup_Clicked(Target As String)
    End Sub

    Sub ABMRange_Changed(start as String, stop as String)
    End Sub

    Sub ABMReport_Build(internalPage as ABMPage, internalID as String)
    End Sub

    Sub ABMReport_CleanUp(internalPage as ABMPage, internalID as String)
    End Sub

    Sub ABMReport_FirstRun(internalPage as ABMPage, internalID as String)
    End Sub

    Sub ABMReport_Refresh(internalPage as ABMPage, internalID as String)
    End Sub

    Sub ABMSVGSurface_SVGClicked(elementID as String)
    End Sub

    Sub ABMSVGSurface_SVGTicked(timerID as String, extra as String)
    End Sub

    Sub ABMSlider_Changed(value as String)
    End Sub

    Sub ABMSmartWizard_NavigationFinished(ReturnName as String)
    End Sub

    Sub ABMSmartWizard_NavigationToStep(fromReturnName As String, toReturnName as String)
    End Sub

    Sub ABMSwitch_Clicked(Target As String)
    End Sub

    Sub ABMTable_Changed(Params as Map)
    End Sub

    Sub ABMTable_Clicked(PassedRowsAndColumns as List)
    End Sub

    Sub ABMTable_SortChanged(DataField as String, Order as String)
    End Sub

    Sub ABMTableMutable_Changed(Params as Map)
    End Sub

    Sub ABMTableMutable_Clicked(PassedRowsAndColumns as List)
    End Sub

    Sub ABMTableMutable_SortChanged(DataField as String, Order as String)
    End Sub

    Sub ABMTabs_Clicked(TabReturnName as String)
    End Sub

    Sub ABMTimeLine_Added(uniqueID as String)
    End Sub

    Sub ABMTimeLine_BackToStart()
    End Sub

    Sub ABMTimeLine_DataLoaded()
    End Sub

    Sub ABMTimeLine_MovedToSlide(uniqueID as String)
    End Sub

    Sub ABMTimeLine_Removed(uniqueID as String)
    End Sub

    Sub ABMTreeTable_Clicked(TreeRowId as String, TreeCellId as String)
    End Sub

    Sub ABMTreeTable_Dropped(Params as Map)
    End Sub

    Sub ABMTreeTable_NeedsRefreshChildren(rowId As String)
    End Sub

    Sub ABMVideo_YouTubeStateChanged(State As Int)
    End Sub
    amaxco and Harris like this.
  1. This site uses cookies to help personalise content, tailor your experience and to keep you logged in if you register.
    By continuing to use this site, you are consenting to our use of cookies.
    Dismiss Notice