B4J Tutorial [ABMaterial] Better Delegating the creation of complex components

Should the "Composite" or "Complex" component Class be implemented in v3.75?

  • YES!

    Votes: 11 91.7%
  • NO!

    Votes: 0 0.0%
  • What For?

    Votes: 1 8.3%

  • Total voters
    12

Cableguy

Expert
Licensed User
[EDIT: This thread started as my approach to delegating complex components creation, including events.
@alwaysbusy then proposed a better system, that he needs us to say if we want it implemented on the upcoming release of v 3.75, SO let's create a Poll!
Hi Gurus...

This time I come Not with questions, but with MY answer to one of my biggest problems with this amazing framework : ABMaterial!

And to me, the main problem is, a simple webapp can get quite complex very quickly UI wise, so maintainability can be a very hard business...

So I thought... what if I could divide my code into "complex components" and treat them as a unit, instead of going through all of a pages lines to find a prop to change, and risk breaking the code beyond repair!
So I obviously thought of a ClassModule and treat it like a custom view... and it almost worked...
I only had 2 issues with it... both related to theming...
Going the Class route I just couldn't figure out how to theme the "complex component", since a class needs to be first initialized, and the theme must be created BEFORE!
Then, as I typed this question I started thinking about a CodeModule...
Main advantages : doesn't need declaring nor initializing!

So, my basic steps were:
Create a Code Module, in my case, I called it "TopBar2"

then declared all that was needed to my Complex Component
B4X:
Sub Process_Globals
    Private ABM As ABMaterial 'ignore
    Private MyTheme As ABMTheme

    Private TargetPage As ABMPage
    Private ObjectId As String
    Private LogoFilename As String
    Private TopBarThemeName As String

    Private TopBarContainer As ABMContainer
    Private TopBarSubContainer As ABMContainer
    Private TopBarLogo As ABMImage
    Private TopBarSearch As ABMInput
    Private TopBarBtn1 As ABMButton
    Private TopBarBtn2 As ABMButton
    Private TopBarBtn3 As ABMButton

    Private LogoTheme As String
    Private SearchTheme As String
    Private BtnTheme As String
    Private Btn3Theme As String
End Sub
Then the "real construction work" takes place at the "CreateTopBar" sub:
B4X:
Public Sub CreateTopBar(page As ABMPage, id As String, filename As String, Theme As String) As ABMContainer
    TargetPage = page
    ObjectId = id
    LogoFilename = filename
    TopBarThemeName = Theme
    LogoTheme = TopBarThemeName & "Logo"
    SearchTheme = TopBarThemeName & "Search"
    BtnTheme = TopBarThemeName & "Btn"
    Btn3Theme = TopBarThemeName & "Btn3"

    TopBarContainer.Initialize ( TargetPage, ObjectId, "" )
    TopBarSubContainer.Initialize ( TargetPage, ObjectId & "SubCntr", "")
    TopBarLogo.Initialize ( TargetPage, ObjectId & "Logo", LogoFilename, 1 )
    TopBarSearch.Initialize ( TargetPage, ObjectId & "Search", ABM.INPUT_SEARCH, "Pesquizar", False, SearchTheme )
    TopBarBtn1.InitializeRaised ( TargetPage, ObjectId & "Btn1", "", "", "Registar", BtnTheme )
    TopBarBtn2.InitializeRaised( TargetPage, ObjectId & "Btn2", "", "", "Login", BtnTheme)
    TopBarBtn3.InitializeFlat ( TargetPage, ObjectId & "Btn3", "settings", ABM.ICONALIGN_CENTER, "", Btn3Theme)

    LogError ("LogoTheme : " & LogoTheme)
    LogError ("SearchTheme : " & SearchTheme)
    LogError ("BtnTheme : " & BtnTheme)
    LogError ("Btn3Theme : " & Btn3Theme)

    'then we build the grid

    TopBarContainer.AddRowsMV(1, False,7,7, ABM.VISIBILITY_SHOW_ON_LARGE_ONLY,"").AddCellsOS(1,0,0,0,2,2,2,"").AddCellsOS(1,0,0,0,7,7,7,"").AddCellsOS(1,0,0,0,3,3,3,"")
    TopBarContainer.BuildGrid ' IMPORTANT!

    TopBarSubContainer.AddRowsMV(1, False,0,0, ABM.VISIBILITY_SHOW_ON_LARGE_ONLY,"").AddCellsOSMP(1,0,0,0,5,5,5,0,0,0,5,"").AddCellsOSMP(1,0,0,0,5,5,5,0,0,5,0,"").AddCellsOS(1,0,0,0,1,1,1,"")
    TopBarSubContainer.BuildGrid ' IMPORTANT!

    'and we set the cell height
    TopBarContainer.Row(1).SetFixedHeight(70, False)
'    TopBarSubContainer.Row(1).SetFixedHeight(60, False)

    'we then add the picture Logo and set its theme
    TopBarContainer.Cell(1,1).AddComponent(TopBarLogo)
    TopBarContainer.Cell(1,1).UseTheme(LogoTheme)

    'Now we add the search field and set its theme
    TopBarSearch.Narrow = True
    TopBarContainer.Cell(1,2).AddComponent(TopBarSearch)
    TopBarContainer.Cell(1,2).UseTheme(SearchTheme)

    'now we add the SubContainer for Buttons 1 & 2
    TopBarContainer.Cell(1,3).AddComponent(TopBarSubContainer)

    'finaly we start adding the buttons
    TopBarBtn1.UseFullCellWidth =True
    TopBarSubContainer.Cell(1,1).AddComponent(TopBarBtn1)

    TopBarBtn2.UseFullCellWidth =True
    TopBarSubContainer.Cell(1,2).AddComponent(TopBarBtn2)
    TopBarContainer.Cell(1,3).UseTheme(BtnTheme)

    TopBarSubContainer.Cell(1,3).AddComponent(TopBarBtn3)

    Return TopBarContainer
End Sub
(yeah, I know, not the best coding technique, but I'm getting there!)
The more aware of you will notice that I have 2 containers...
The main Container has 3 cells, and inside the 3rd cell we have a SubContainer with another 3 cells...
I did it this way to arrange the positioning of the components more efficiently (in other words, to my liking)
Then we do what we always do, assemble the LEGO! and we return the main Container to the calling sub!

the call resembles something like this:
B4X:
Page.Cell(1,1).AddComponent(TopBar2.CreateTopBar(AppPage,"TopBar","../web/images/topbarlogo60.gif","TopBar"))
The way I solved my theming issue was, I first created a "BuildTheme" public sub:
B4X:
Public Sub BuildTheme
    MyTheme =ABMShared.MyTheme

    MyTheme.AddCellTheme("TopBarLogo")
    MyTheme.Cell("TopBarLogo").Align = ABM.CELL_ALIGN_CENTER
    MyTheme.Cell("TopBarLogo").VerticalAlign = True

    MyTheme.AddInputTheme("TopBarSearch")
    MyTheme.Input("TopBarSearch").BackColor = ABM.COLOR_GREY
    MyTheme.Input("TopBarSearch").BackColorIntensity = ABM.INTENSITY_LIGHTEN2
    MyTheme.Input("TopBarSearch").ForeColor = ABM.COLOR_BLUE
    MyTheme.Input("TopBarSearch").ForeColorIntensity = ABM.INTENSITY_DARKEN4
    MyTheme.Input("TopBarSearch").FocusForeColor = ABM.COLOR_BLUE
    MyTheme.Input("TopBarSearch").FocusForeColorIntensity = ABM.INTENSITY_DARKEN4
    MyTheme.Input("TopBarSearch").AutoCompleteBackColor = ABM.COLOR_DEEPPURPLE
    MyTheme.AddCellTheme("TopBarSearch")
    MyTheme.Cell("TopBarSearch").Align = ABM.CELL_ALIGN_CENTER
    MyTheme.Cell("TopBarSearch").VerticalAlign = True

    MyTheme.AddCellTheme("TopBarBtn")

    MyTheme.Cell("TopBarBtn").Align = ABM.CELL_ALIGN_CENTER
    MyTheme.Cell("TopBarBtn").VerticalAlign = True
    MyTheme.AddButtonTheme("TopBarBtn")
    MyTheme.Button("TopBarBtn").WavesCircle = True

    MyTheme.AddCellTheme("TopBarBtn3")
    MyTheme.Cell("TopBarBtn3").Align = ABM.CELL_ALIGN_CENTER
    MyTheme.Cell("TopBarBtn3").VerticalAlign = True
    MyTheme.AddButtonTheme("TopBarBtn3")
    MyTheme.Button("TopBarBtn3").BackColor = ABM.COLOR_GREY
    MyTheme.Button("TopBarBtn3").BackColorIntensity = ABM.INTENSITY_LIGHTEN2
    MyTheme.Button("TopBarBtn3").WavesCircle = False
    MyTheme.Button("TopBarBtn3").WavesEffect = ABM.WAVESEFFECT_NONE
    MyTheme.Button("TopBarBtn3").ForeColor = ABM.COLOR_BLUE
    MyTheme.Button("TopBarBtn3").ForeColorIntensity = ABM.INTENSITY_DARKEN4
End Sub
But the real magic in this was the very first line... that extended the existing theme into my Code Module
All I had the to do was call this sub from within the Shared CodeModule BuildTheme sub.
The only issue I still have is, I can't figure out how to theme the subcontainer.

Then, to make things a bit cleaner on the events side...
B4X:
Sub Page_ParseEvent(Params As Map)
    Dim eventName As String = Params.Get("eventname")
    Dim eventParams() As String = Regex.Split(",",Params.Get("eventparams"))
...
    If eventName = "topbarbtn1_clicked" Then
        CallSub2 (myTopBar, "TopBarBtn1_Clicked",Params.Get(eventParams(0)))
    End If
This way we can have the events declared inside our CodeModule keeping it all nice and neat!

Hope this helps somebody!
 
Last edited:

Harris

Well-Known Member
Licensed User
Hope this helps somebody!
Got a simple picture of what this looks like?

If eventName = "topbarbtn1_clicked"ThenCallSub2 (myTopBar, "TopBarBtn1_Clicked",Params.Get(eventParams(0)))EndIf

This is interesting... Something like "if it is a table".... Events in the CM, nice. I have not taken the time to strip the build theme and stick it in a code module (like shared). I really don't see the need for each page to have a buildtheme since since each new page is a repeat of the previous construction. At times, one gets lost in what NEEDS to be with the page and what can be shared... So, I go with status quo... and "rinse and repeat"...

Thanks
 

alwaysbusy

Expert
Licensed User
It is 4am and I really should get some sleep, but your approach gave me an idea I'm going to test out. Report back soon...
 

Harris

Well-Known Member
Licensed User
4 am, don't you work at 6?
Your over active creative mind keeps us all up.
We all know how much more productive we could be if sleep was not an imperative.

In your case (and mine), give a dog a bone - then try and take it away... I feel your ambition. That is what I admire.
@Cableguy has been quite busy questioning / testing and exploring above and beyond the fundamentals. Excellent.

Without this feedback, your ideas cannot foster alone. That is why, i suspect, he is ranked as "expert", rightfully so.

You once advised me to shut it down and reconsider ( complicated matter) - which I did and after reconsideration, all was clear.

It - ABM only gets better....
 

alwaysbusy

Expert
Licensed User
don't you work at 6?
It is going to be an all nighter I think. But it is weekend after tomorrow (today?)!

You once advised me to shut it down and reconsider
I know! But I'm not known to take my own advice...;)

But if I don't look look into it right away, I get the weirdest dreams. Once woke up in sweat as I was searching for an algorithm to zip refugees so they could fit on my USB stick. Took me a whole minute to figure out humans could not be compressed at all :confused:
 

Harris

Well-Known Member
Licensed User
It is going to be an all nighter I think. But it is weekend after tomorrow (today?)!


I know! But I'm not known to take my own advice...;)

But if I don't look look into it right away, I get the weirdest dreams. Once woke up in sweat as I was searching for an algorithm to zip refugees so they could fit on my USB stick. Took me a whole minute to figure out humans could not be compressed at all :confused:
Sheat.. I get these weird programming dreams as well. Not as off as yours, but difficult tasks that could be resolve thru dreams... Weird shit... The mind just won't shut down and let us play normal.

Take a break....
 

mindful

Active Member
Licensed User
So, my basic steps were:
Create a Code Module, in my case, I called it "TopBar2"
I think your solution would not work properly because you created a code module. Take the following scenario:

You get one connection to your page. -> the page calls the TopBar2.CreateTopBar which saves references as private in the code module for the Target page and returns your top bar.

You get another connection to your page -> the page calls the TopBar2.CreateTopBar which replaces all the private references saved with the above connection.

So as a fix I suggest to declare your variables inside the method. And also if withing your events (TopBarBtn1_Clicked) declared in the code module you access private or public variables declared in the code module you should replace CallSub with CallSubDelayed because the pages that call the subs in the code module run on different thread.

This is just a sugestion as I don't know what you are trying to acomplish ... and maybe in your situation your code will work.
 

Cableguy

Expert
Licensed User
I have tried connecting to the webapp from several browsers and had no issues with it...
This particular component will only be used in this one page (my webapp will have only this one page), and, as I stated, this is MY solution to MY particular problem, and it works as I expected (except for the subcontainer theme thing)

Treat this as a proof of concept, anyone that uses this concept will need to adapt it to his particular needs
 

alwaysbusy

Expert
Licensed User
Here is my suggestion on how we could do this. It has limitations: it is not possible to use AddArrayComponent() in the class, nor can the resulting component be added with AddArrayComponent()! I'm not saying it will never be possible, however building this for AddArrayComponent WILL break existing code heavely!

Ok, here we go with my proposed solution:

I created a Class (not a module, so we can re-use this component). As Mindful pointed out, in a module, all properties in Global_ are shared with all the logged in users (I would be very surprised if they are not). I used Cableguys example for this.
B4X:
'Class module
Sub Class_Globals
   Private ABM As ABMaterial 'ignore
 
   Private TargetPage As ABMPage
   Private ObjectId As String
   Private LogoFilename As String
   Private TopBarThemeName As String

   Public TopBarContainer As ABMContainer ' has to be public, so we can use it in the AddComponent() method
   Private TopBarSubContainer As ABMContainer
   Private TopBarLogo As ABMImage
   Private TopBarSearch As ABMInput
   Private TopBarBtn1 As ABMButton
   Private TopBarBtn2 As ABMButton
   Private TopBarBtn3 As ABMButton

   Private LogoTheme As String
   Private SearchTheme As String
   Private BtnTheme As String
   Private Btn3Theme As String
End Sub

' new part, we give the class it's own ParseEvent method
public Sub ParseEvent(params As Map)
   Dim eventName As String = params.Get("eventname")
   Dim eventParams() As String = Regex.Split(",",params.Get("eventparams"))

   ' Important! Here we remove the 'parent' part, so we can re-use this component, as the events will have the same name
   ' e.g. myTopBar2aBtn1_Clicked will become Btn1_Clicked
   eventName = eventName.SubString(ObjectId.Length)

   If SubExists(Me, eventName) Then
     params.Remove("eventname")
     params.Remove("eventparams")
     Select Case params.Size
       Case 0
         CallSub(Me, eventName)
       Case 1
         CallSub2(Me, eventName, params.Get(eventParams(0)))
       Case 2
         If params.get(eventParams(0)) = "abmistable" Then
           Dim PassedTables As List = ABM.ProcessTablesFromTargetName(params.get(eventParams(1)))
           CallSub2(Me, eventName, PassedTables)
         Else
           CallSub3(Me, eventName, params.Get(eventParams(0)), params.Get(eventParams(1)))
         End If
       Case Else
         ' cannot be called directly, to many param
         CallSub2(Me, eventName, params)
     End Select
   End If
End Sub

'Initializes the object. You can add parameters to this method if needed.
Public Sub Initialize(page As ABMPage, id As String, filename As String, Theme As String)
   TargetPage = page
   ObjectId = id
   LogoFilename = filename
   TopBarThemeName = Theme
   LogoTheme = TopBarThemeName & "Logo"
   SearchTheme = TopBarThemeName & "Search"
   BtnTheme = TopBarThemeName & "Btn"
   Btn3Theme = TopBarThemeName & "Btn3"

   TopBarContainer.Initialize ( TargetPage, ObjectId, "" )
   TopBarSubContainer.Initialize ( TargetPage, ObjectId & "SubCntr", "")
   TopBarLogo.Initialize ( TargetPage, ObjectId & "Logo", LogoFilename, 1 )
   TopBarSearch.Initialize ( TargetPage, ObjectId & "Search", ABM.INPUT_SEARCH, "Pesquizar", False, SearchTheme )
   TopBarBtn1.InitializeRaised ( TargetPage, ObjectId & "Btn1", "", "", "Registar", BtnTheme )
   TopBarBtn2.InitializeRaised( TargetPage, ObjectId & "Btn2", "", "", "Login", BtnTheme)
   TopBarBtn3.InitializeFlat ( TargetPage, ObjectId & "Btn3", "settings", ABM.ICONALIGN_CENTER, "", Btn3Theme)

   ' NEW PART: we set for each component that we want the handler of the events here to this class
   TopBarContainer.EventHandler = Me
   TopBarSubContainer.EventHandler = Me
   TopBarLogo.EventHandler = Me
   TopBarSearch.EventHandler = Me
   TopBarBtn1.EventHandler = Me
   TopBarBtn2.EventHandler = Me
   TopBarBtn3.EventHandler = Me

   'then we build the grid
   TopBarContainer.AddRowsMV(1, False,7,7, ABM.VISIBILITY_SHOW_ON_LARGE_ONLY,"").AddCellsOS(1,0,0,0,2,2,2,"").AddCellsOS(1,0,0,0,7,7,7,"").AddCellsOS(1,0,0,0,3,3,3,"")
   TopBarContainer.BuildGrid ' IMPORTANT!

   TopBarSubContainer.AddRowsMV(1, False,0,0, ABM.VISIBILITY_SHOW_ON_LARGE_ONLY,"").AddCellsOSMP(1,0,0,0,5,5,5,0,0,0,5,"").AddCellsOSMP(1,0,0,0,5,5,5,0,0,5,0,"").AddCellsOS(1,0,0,0,1,1,1,"")
   TopBarSubContainer.BuildGrid ' IMPORTANT!

   'and we set the cell height
   TopBarContainer.Row(1).SetFixedHeight(70, False)
   '  TopBarSubContainer.Row(1).SetFixedHeight(60, False)

   'we then add the picture Logo and set its theme
   TopBarContainer.Cell(1,1).AddComponent(TopBarLogo)
   TopBarContainer.Cell(1,1).UseTheme(LogoTheme)

   'Now we add the search field and set its theme
   TopBarSearch.Narrow = True
   TopBarContainer.Cell(1,2).AddComponent(TopBarSearch)
   TopBarContainer.Cell(1,2).UseTheme(SearchTheme)

   'now we add the SubContainer for Buttons 1 & 2
   TopBarContainer.Cell(1,3).AddComponent(TopBarSubContainer)

   'finaly we start adding the buttons
   TopBarBtn1.UseFullCellWidth =True
   TopBarSubContainer.Cell(1,1).AddComponent(TopBarBtn1)

   TopBarBtn2.UseFullCellWidth =True
   TopBarSubContainer.Cell(1,2).AddComponent(TopBarBtn2)
   TopBarContainer.Cell(1,3).UseTheme(BtnTheme)

   TopBarSubContainer.Cell(1,3).AddComponent(TopBarBtn3)

   TopBarSubContainer.Row(1).SetFixedHeight(70, False) ' I'm guessing here for the SubContainer theme problem. You want to center it with the Search component?
   TopBarSubContainer.Cell(1,1).UseTheme(BtnTheme)
   TopBarSubContainer.Cell(1,1).MarginTop = "20px"
   TopBarSubContainer.Cell(1,2).UseTheme(BtnTheme)
   TopBarSubContainer.Cell(1,2).MarginTop = "20px"
   TopBarSubContainer.Cell(1,3).UseTheme(BtnTheme)
   TopBarSubContainer.Cell(1,3).MarginTop = "20px"

End Sub

Sub Btn1_Clicked(Target As String) ' note we use Btn1_Clicked, NOT objectID & Btn1_Clicked (which is impossible anyway)
   Log("You clicked Btn1 from " & ObjectId)
End Sub
So this is basically the same code with a new one: its own ParseEvent() method. Note one BIG difference in this method:
B4X:
eventName = eventName.SubString(ObjectId.Length)
By doing this, be can re-use this component all we want, as the event names can be a fixed name. For both myTopBar2A and myTopBar2B (see further), they can use Btn1, Btn2, etc for the event name.
Also, in the Initialize, each component would get a property EventHandler which points to this class. Check out the comments in the code, ask away if in doubt.

And now we can create our Btn1_Clicked() event in the class instead of in the main page class.

In the Page, a small modification is made to the Page_ParseEvent() method:
B4X:
Sub Page_ParseEvent(Params As Map)
   Dim eventName As String = Params.Get("eventname")
   Dim eventParams() As String = Regex.Split(",",Params.Get("eventparams"))
   'Log(eventName)
   If eventName = "beforeunload" Then
     Log("preparing for url refresh")
     ABM.RemoveMeFromCache(ABMShared.CachedPages, ABMPageId)
     Return
   End If
   Dim caller As Object = page.GetEventHandler(Me, eventName) ' NEW
   If caller = Me Then ' NEW
     If SubExists(Me, eventName) Then
       Params.Remove("eventname")
       Params.Remove("eventparams")
       ' BEGIN NEW DRAGDROP
       If eventName = "page_dropped" Then
         page.ProcessDroppedEvent(Params)
       End If
       ' END NEW DRAGDROP
       Select Case Params.Size
         Case 0
           CallSub(Me, eventName)
         Case 1
           CallSub2(Me, eventName, Params.Get(eventParams(0)))
         Case 2
           If Params.get(eventParams(0)) = "abmistable" Then
             Dim PassedTables As List = ABM.ProcessTablesFromTargetName(Params.get(eventParams(1)))
             CallSub2(Me, eventName, PassedTables)
           Else
             CallSub3(Me, eventName, Params.Get(eventParams(0)), Params.Get(eventParams(1)))
           End If
         Case Else
           ' cannot be called directly, to many param
           CallSub2(Me, eventName, Params)
       End Select
     End If
   Else
     CallSubDelayed2(caller, "ParseEvent", Params)  ' NEW
   End If
End Sub
So new is now the page.GetEventHandler() method. It retrieves the Class you've set with the component.EventHandler property before.

Creating and adding components to the page is similar as we're used to:
B4X:
Dim myTopBar2A As TopBar2
myTopBar2A.Initialize(page, "myTopBar2A", "../images/batman.png", "TopBar")
page.Cell(2,1).AddComponent(myTopBar2A.TopBarContainer)

Dim myTopBar2B As TopBar2
myTopBar2B.Initialize(page, "myTopBar2B", "../images/superman.png", "TopBar")
page.Cell(3,1).AddComponent(myTopBar2B.TopBarContainer)
Note: As this is now a class, BuildTheme for this component cannot be set in the class itself. It needs to be in the page (or in a module, using Cableguys system). This is because the CSS needs to generated BEFORE the page is loaded.

In my example, I did it in the page:
B4X:
public Sub BuildTheme()
   ' start with the base theme defined in ABMShared
   theme.Initialize("pagetheme")
   theme.AddABMTheme(ABMShared.MyTheme)

   BuildThemeForTopBar
End Sub

Public Sub BuildThemeForTopBar()
   theme.AddCellTheme("TopBarLogo")
   theme.Cell("TopBarLogo").Align = ABM.CELL_ALIGN_CENTER
   theme.Cell("TopBarLogo").VerticalAlign = True
   ...
End Sub
The result for this system is that now the events for e.g. Btn1 are raised in the class:



If you guys like this, I can start building it for version 3.75 this weekend.

Also @Cableguy: The sub container Theme looks like it is working to me, you just didn't use it. Or what I am missing?

Alwaysbusy
 
Last edited:

alwaysbusy

Expert
Licensed User
Also this does not break your existing code, as the page.GetEventHandler() method will return Me and act as usual.
 

incendio

Well-Known Member
Licensed User
Here is my suggestion on how we could do this. It has limitations: it is not possible to use AddArrayComponent() in the class, nor can the resulting component be added with AddArrayComponent()! I'm not saying it will never be possible, however building this for AddArrayComponent WILL break existing code heavely!
I have not much experience with ABMaterial, - still in learning process.
Till now, never use AddArrayComponent(), so unable to use this on class have no effect on me.

I do have a class, made from ABMInput & ABMTable.

For ABMInput's class, didn't have any problem at all, though class couldn't catch any events, but all events still catch at page level where I can & intended to put my codes for events on page level.

For ABMTable's class, I do have a problem, which is events were not catch on class level and on page level. I do have a solution for this problem, but it was kind of tricky.

So, I am eagerly waiting for your proposed solution to be implemented on new version of ABMaterial :)
 

OliverA

Expert
Licensed User
Disclaimer: Have not really used ABM yet, I'm just chiming in to see if the code consolidation can be taken even further.

How about moving the Page_ParsEvent code to the shared module and only having a stub that calls this code in the page?
In the actual page class have:
B4X:
Sub Page_ParseEvent(Params as Map)
   ' Note: I'm using "SharedModule" here, but this needs
   '    to be the name of the actual shared module
    CallSub3(SharedModule, "Page_ParseEvent", CreateMap("pageid" : ABMPageId, "page":Page, "caller":Me), Params)
Sub
In the shared module have this:
B4X:
' The word "Modded" indicates lines previously posted that have been, well, modded
' The words "NEW NEW" indicate lines that have been added
Sub Page_ParseEvent(CallerInfo As Map, Params As Map) ' Modded
   Dim eventName As String = Params.Get("eventname")
   Dim eventParams() As String = Regex.Split(",",Params.Get("eventparams"))
   Dim pageID As String = CallerInfo.Get("pageid") ' NEW NEW
   'Log(eventName)
   If eventName = "beforeunload" Then
     Log("preparing for url refresh")
     ABM.RemoveMeFromCache(ABMShared.CachedPages, pageID) ' Modded
     Return
   End If
   Dim pageObject As ABMPAge = CallerInfo.Get("page") ' NEW NEW
   Dim callerObject As Object = CallerInfo.Get("caller") ' NEW NEW
   Dim caller As Object = page.GetEventHandler(callerObject, eventName) ' NEW Modded
   If caller = callerObject Then ' NEW Modded
     If SubExists(caller, eventName) Then ' Modded
       Params.Remove("eventname")
       Params.Remove("eventparams")
       ' BEGIN NEW DRAGDROP
       If eventName = "page_dropped" Then
         pageObject.ProcessDroppedEvent(Params) ' Modded
       End If
       ' END NEW DRAGDROP
       Select Case Params.Size
         Case 0
           CallSub(caller, eventName) ' Modded
         Case 1
           CallSub2(caller, eventName, Params.Get(eventParams(0))) ' Modded
         Case 2
           If Params.get(eventParams(0)) = "abmistable" Then
             Dim PassedTables As List = ABM.ProcessTablesFromTargetName(Params.get(eventParams(1)))
             CallSub2(caller, eventName, PassedTables) ' Modded
           Else
             CallSub3(caller, eventName, Params.Get(eventParams(0)), Params.Get(eventParams(1))) ' Modded
           End If
         Case Else
           ' cannot be called directly, to many param
           CallSub2(caller, eventName, Params) ' Modded
       End Select
     End If
   Else
     CallSubDelayed2(caller, "ParseEvent", Params)  ' NEW
   End If
End Sub
Please note this is untested code and may have typos, logic errors, and/or syntax errors. I also don't know if this would have some sort of impact on threading and therefore may not be a suitable way of doing things (if it is suitable to begin with).

Why did I create a CallerInfo map? Because I don't know how to pass on a pages class module and then still get to ABMPageID and Page variables (since each page's class module has a different class name). So there may even be a cleaner way to call the shared method that just escapes my limited experience with/knowledge of B4J.
 

Cableguy

Expert
Licensed User
Hi Alain, I'm trying to test out your proposed solution, but it seems that, in ver 3.50, "GetEventHandler" does not exist yet...
 

alwaysbusy

Expert
Licensed User
No it does not. It is only if you guys approve this system, I will build it in 3.75. This is just a proof of concept I've written. Making this work for real is a lot more work.
 

Cableguy

Expert
Licensed User
I've just created a poll to encourage @alwaysbusy to continue his highly appreciated hard and good work, and to gently ask him to add this new feature to the upcoming v3.75
 
Last edited:

mindful

Active Member
Licensed User
@alwaysbusy so if you do implement it this way will it be necessary to set .EventHandler for every component we create even if we do not wish to delegate ?

Maybe it can be Null by default and in parseevents of the page it can be if caller = Null or caller = Me then .... else ...

Just a thought ;)
 

alwaysbusy

Expert
Licensed User
@mindful this indeed how it will work (see post #10). If you do not want to delegate, you don't have to do anything. That way it will not break existing projects too. By passing me in the GetEventHandler, it sort of acts like a map.GetDefault method.
 
Top