B4J Question BaNano Custom View Question

walterf25

Expert
Licensed User
Longtime User
Hi all, i just recently started playing around with the BaNano framework, I tried creating a custom view, basically just a simple Top Navigation Bar.

The question I have is simple, how can I handle the click events on any of the Menu items when clicked?

Below is my code for the custom View class:


topnav:
'Custom BANano View class

'Uncomment the events you want to show to the user and implement the HandleEvents in DesignerCreateView
'#Event: Focus (event As BANanoEvent)
'#Event: Blur (event As BANanoEvent)
'#Event: Resize (event As BANanoEvent)
'#Event: Scroll (event As BANanoEvent)
'#Event: Keydown (event As BANanoEvent)
'#Event: KeyPress (event As BANanoEvent)
'#Event: KeyUp (event As BANanoEvent)
#Event: Click (event As BANanoEvent)
'#Event: ContextMenu (event As BANanoEvent)
'#Event: Dblclick (event As BANanoEvent)
'#Event: MouseDown (event As BANanoEvent)
'#Event: MouseEnter (event As BANanoEvent)
'#Event: MouseLeave (event As BANanoEvent)
'#Event: MouseMove (event As BANanoEvent)
'#Event: MouseOver (event As BANanoEvent)
'#Event: MouseOut (event As BANanoEvent)
'#Event: MouseUp (event As BANanoEvent)
'#Event: Wheel (event As BANanoEvent)
'#Event: Drag (event As BANanoEvent)
'#Event: DragEnd (event As BANanoEvent)
'#Event: DragEnter (event As BANanoEvent)
'#Event: DragStart (event As BANanoEvent)
'#Event: DragLeave (event As BANanoEvent)
'#Event: DragOver (event As BANanoEvent)
'#Event: Drop (event As BANanoEvent)
'#Event: TouchCancel (event As BANanoEvent)
'#Event: TouchEnd (event As BANanoEvent)
'#Event: TouchEnter (event As BANanoEvent)
'#Event: TouchLeave (event As BANanoEvent)
'#Event: TouchMove (event As BANanoEvent)
'#Event: TouchStart (event As BANanoEvent)
'#Event: Change (event As BANanoEvent)

' Properties that will be show in the ABStract Designer.  They will be passed in the props map in DesignerCreateView (Case Sensitive!)
#DesignerProperty: Key: Classes, DisplayName: Classes, FieldType: String, DefaultValue: , Description: Classes added to the HTML tag.
#DesignerProperty: Key: Style, DisplayName: Style, FieldType: String, DefaultValue: , Description: Styles added to the HTML tag. Must be a json String.
#DesignerProperty: Key: MarginLeft, DisplayName: Margin Left, FieldType: String, DefaultValue: , Description: Margin Left
#DesignerProperty: Key: MarginRight, DisplayName: Margin Right, FieldType: String, DefaultValue: , Description: Margin Right
#DesignerProperty: Key: MarginTop, DisplayName: Margin Top, FieldType: String, DefaultValue: , Description: Margin Top
#DesignerProperty: Key: MarginBottom, DisplayName: Margin Bottom, FieldType: String, DefaultValue: , Description: Margin Bottom
#DesignerProperty: Key: PaddingLeft, DisplayName: Padding Left, FieldType: String, DefaultValue: , Description: Padding Left
#DesignerProperty: Key: PaddingRight, DisplayName: Padding Right, FieldType: String, DefaultValue: , Description: Padding Right
#DesignerProperty: Key: PaddingTop, DisplayName: Padding Top, FieldType: String, DefaultValue: , Description: Padding Top
#DesignerProperty: Key: PaddingBottom, DisplayName: Padding Bottom, FieldType: String, DefaultValue: , Description: Padding Bottom
#DesignerProperty: Key: Menu, DisplayName: Menu, FieldType: String, DefaultValue: Home, List: Home|Report, Description: Adds Menus to Navigation Bar.
Sub Class_Globals
    Private BANano As BANano 'ignore
    Private mName As String 'ignore
    Private mEventName As String 'ignore
    Private mCallBack As Object 'ignore
    Private mTarget As BANanoElement 'ignore
    Private mElement As BANanoElement 'ignore
   
    Private mClasses As String = ""
    Private mStyle As String = ""
    Public MarginLeft As String = ""
    Public MarginRight As String = ""
    Public MarginTop As String = ""
    Public MarginBottom As String = ""
    Public PaddingLeft As String = ""
    Public PaddingRight As String = ""
    Public PaddingTop As String = ""
    Public PaddingBottom As String = ""
End Sub

Public Sub Initialize (CallBack As Object, Name As String, EventName As String)
    mName = Name
    mEventName = EventName.ToLowerCase
    mCallBack = CallBack  
    Log("eventName: " & mEventName)
End Sub

' this is the place where you create the view in html and run initialize javascript
Public Sub DesignerCreateView (Target As BANanoElement, Props As Map)
    mTarget = Target

    If Props <> Null Then
        mClasses = Props.Get("Classes")
        mStyle = Props.Get("Style")
        MarginLeft = Props.Get("MarginLeft")
        MarginRight = Props.Get("MarginRight")
        MarginTop = Props.Get("MarginTop")
        MarginBottom = Props.Get("MarginBottom")
        PaddingLeft = Props.Get("PaddingLeft")
        PaddingRight = Props.Get("PaddingRight")
        PaddingTop = Props.Get("PaddingTop")
        PaddingBottom = Props.Get("PaddingBottom")
    End If
   
    Dim exStyle As String = BuildExStyle
    mTarget.Append($"<div class="${mName}">"$)
    Dim menu As String = Props.Get("Menu")
    mTarget.Append($"<a class="active" href=$"#news">${menu}</a>"$)
    mTarget.Append("</div>")
    mElement = mTarget.Get("#" & mName)
   
    '' defining events is very simple. Note that it has to be run AFTER adding it to the HTML DOM! eventName must be lowercase!
    mElement.HandleEvents("click", Me, mEventName & "_click")
    'mElement.HandleEvents("mouseenter", Me, "mouseenter")
End Sub

public Sub AddToParent(targetID As String)
    mTarget = BANano.GetElement("#" & targetID.ToLowerCase)
    DesignerCreateView(mTarget, Null)
End Sub

public Sub Remove()
    mTarget.Empty
    BANano.SetMeToNull
End Sub

public Sub Trigger(event As String, params() As String)
    If mElement <> Null Then
        mElement.Trigger(event, params)
    End If
End Sub

public Sub BuildExStyle() As String
    Dim sb As StringBuilder
    sb.Initialize
    If MarginLeft <> "" Then sb.Append("margin-left: " & MarginLeft & ";")
    If MarginRight <> "" Then sb.Append("margin-right: " & MarginRight & ";")
    If MarginTop <> "" Then sb.Append("margin-top: " & MarginTop & ";")
    If MarginBottom <> "" Then sb.Append("margin-bottom: " & MarginBottom & ";")
    If PaddingLeft <> "" Then sb.Append("padding-left: " & PaddingLeft & ";")
    If PaddingRight <> "" Then sb.Append("padding-right: " & PaddingRight & ";")
    If PaddingTop <> "" Then sb.Append("padding-top: " & PaddingTop & ";")
    If PaddingBottom <> "" Then sb.Append("padding-bottom: " & PaddingBottom & ";")
    Return sb.ToString
End Sub

#Region Property Getters and Setters
public Sub setClasses(Classes As String)
    If mElement <> Null Then
        mElement.AddClass(Classes)
    End If
    mClasses = Classes
End Sub

public Sub getClasses() As String
    Return mClasses
End Sub

' must be a json string
' e.g. $"{ "width": "200px", "height": "200px", "background": "green", "border-radius": "5px" }"$
public Sub setStyle(Style As String)
    If mElement <> Null Then
        Log("setting style: " & Style)
        mElement.SetStyle(Style)
    End If
    mStyle = Style
End Sub

public Sub getStyle() As String
    Return mStyle
End Sub

Public Sub setMenu(menu As List)
'''    Dim exStyle As String = BuildExStyle
    mTarget.Empty
    Dim body As BANanoElement = BANano.GetElement("#body")
    Dim div As BANanoElement = body.Append($"<div id="div1"></div>"$).Get("#div1")
    div.AddClass(mName)
    Dim link As String = menu.Get(0)
    link = link.ToLowerCase
    div.Append($"<a class="active" href=$"#${link}">${menu.Get(0)}</a>"$)
    For i = 1 To menu.Size - 1
    Dim link As String = menu.Get(i)
    link = link.ToLowerCase
    div.Append($"<a href=$"#${link}">${menu.Get(i)}</a>"$)
    Next
    div.Append("</div>")
    mTarget = div
    mElement = div.Get("#" & mName)
    mElement.HandleEvents("click", Me, mEventName & "_click")
End Sub
#End Region

#Region Internal Events
Sub topnav_click (event As BANanoEvent)
    Log("clicked..." & " " & event.Value)
End Sub
#End Region

The result view looks like this:
eeparts.PNG

I tried the Method:
B4X:
mElement.HandleEvents("click", Me, mEventName & "_click")
But i don't see the event being raised either inside the Custom class or the Main class where the view is being initialized.

I realize i may be missing something as I just started playing with this, but already spent pretty much the whole day trying to figure it out without much luck.

Any ideas, thoughts?

Thanks,
Walter
 

alwaysbusy

Expert
Licensed User
Longtime User
This is how I would solve it. We handle all the 'complex' event stuff in the custom view in innerClicked, and simply pass an index of our menu list back to the class where the LoadLayout was done.

1. We change the event to only pass the index
B4X:
#Event: Click (Clicked as long)

2. In DesignerCreateView, remove the line mElement.HandleEvents... as we will handle the event of each menu item, not on the menu itself

3. The changes needed to SetMenu (I added comments):
B4X:
Public Sub setMenu(menu As List) 
    mTarget.Empty

    Dim body As BANanoElement = BANano.GetElement("#body")
    Dim div As BANanoElement = body.Append($"<div id="div1"></div>"$).Get("#div1")
    div.AddClass(mName)
    Dim link As String = menu.Get(0)
    link = link.ToLowerCase

    ' give each <a> a data-id attribute.  We can later in innerClicked use this to find out on which menu item we clicked
    ' we also add a dummy class 'mytopnavitem'.  This can be used to grab all the menu items to attach the click event. See further.
    div.Append($"<a data-id="0" class="active mytopnavitem" href=$"#${link}">${menu.Get(0)}</a>"$)

    For i = 1 To menu.Size - 1
        Dim link As String = menu.Get(i)
        link = link.ToLowerCase

        ' same as with the first one, but with a new data-id for each one.
        div.Append($"<a data-id="${i}" href=$"#${link}" class="mytopnavitem">${menu.Get(i)}</a>"$)
    Next
    div.Append("</div>")
    mTarget = div
    mElement = div.Get("#" & mName)
 
    ' This is a cool feature from javascript  We can grab all menu items in one go by selecting them with our dummy 'mytopnavitem' class.
    ' Note the . (dot) in our selector, which means a class!
    Dim mItems As BANanoElement
    mItems.Initialize(".mytopnavitem") ' get them all
    ' now we attach the click event to each one of our menu items in one go and redirect it to the innerClick menthod.
    mItems.HandleEvents("click", Me, "innerClicked") 
End Sub

4. The new innerClicked method:
B4X:
Sub innerClicked (event As BANanoEvent) 'ignore
    ' get the current target (on which item was clicked)
    Dim obj As BANanoObject = event.CurrentTarget

    ' convert it to an element so we can get the attribute 'data-id'
    Dim elem As BANanoElement
    elem.Initialize(obj)

    ' get the data-id attribute
    Dim CurrentClicked As String = elem.GetAttr("data-id")
    Log(CurrentClicked)

    ' we can pass it to the real event on our calling class
    BANano.CallSub(mCallBack, mEventName & "_click", Array(CurrentClicked))
End Sub

5. in our calling class:
B4X:
Sub MyTopNav1_Click (Clicked As Long)
    Log("Clicked on: " & Clicked)

    Select case Clicked
    ...
End Sub

Note: I wrote the innerClicked to explain what needs to happen, but once you get familiar with the system, all this can be done in one line:
B4X:
Sub innerClicked (event As BANanoEvent) 'ignore    
    BANano.CallSub(mCallBack, mEventName & "_click", Array(BANano.ToElement(event.CurrentTarget).GetAttr("data-id")))    
End Sub

Alwaysbusy
 
Last edited:
Upvote 0

walterf25

Expert
Licensed User
Longtime User
This is how I would solve it. We handle all the 'complex' event stuff in the custom view in innerClicked, and simply pass an index of our menu list back to the class where the LoadLayout was done.

1. We change the event to only pass the index
B4X:
#Event: Click (Clicked as long)

2. In DesignerCreateView, remove the line mElement.HandleEvents... as we will handle the event of each menu item, not on the menu itself

3. The changes needed to SetMenu (I added comments):
B4X:
Public Sub setMenu(menu As List)
    mTarget.Empty

    Dim body As BANanoElement = BANano.GetElement("#body")
    Dim div As BANanoElement = body.Append($"<div id="div1"></div>"$).Get("#div1")
    div.AddClass(mName)
    Dim link As String = menu.Get(0)
    link = link.ToLowerCase

    ' give each <a> a data-id attribute.  We can later in innerClicked use this to find out on which menu item we clicked
    ' we also add a dummy class 'mytopnavitem'.  This can be used to grab all the menu items to attach the click event. See further.
    div.Append($"<a data-id="0" class="active mytopnavitem" href=$"#${link}">${menu.Get(0)}</a>"$)

    For i = 1 To menu.Size - 1
        Dim link As String = menu.Get(i)
        link = link.ToLowerCase

        ' same as with the first one, but with a new data-id for each one.
        div.Append($"<a data-id="${i}" href=$"#${link}" class="mytopnavitem">${menu.Get(i)}</a>"$)
    Next
    div.Append("</div>")
    mTarget = div
    mElement = div.Get("#" & mName)

    ' This is a cool feature from javascript  We can grab all menu items in one go by selecting them with our dummy 'mytopnavitem' class.
    ' Note the . (dot) in our selector, which means a class!
    Dim mItems As BANanoElement
    mItems.Initialize(".mytopnavitem") ' get them all
    ' now we attach the click event to each one of our menu items in one go and redirect it to the innerClick menthod.
    mItems.HandleEvents("click", Me, "innerClicked")
End Sub

4. The new innerClicked method:
B4X:
Sub innerClicked (event As BANanoEvent) 'ignore
    ' get the current target (on which item was clicked)
    Dim obj As BANanoObject = event.CurrentTarget

    ' convert it to an element so we can get the attribute 'data-id'
    Dim elem As BANanoElement
    elem.Initialize(obj)

    ' get the data-id attribute
    Dim CurrentClicked As String = elem.GetAttr("data-id")
    Log(CurrentClicked)

    ' we can pass it to the real event on our calling class
    BANano.CallSub(mCallBack, mEventName & "_click", Array(CurrentClicked))
End Sub

5. in our calling class:
B4X:
Sub MyTopNav1_Click (Clicked As Long)
    Log("Clicked on: " & Clicked)

    Select case Clicked
    ...
End Sub

Note: I wrote the innerClicked to explain what needs to happen, but once you get familiar with the system, all this can be done in one line:
B4X:
Sub innerClicked (event As BANanoEvent) 'ignore   
    BANano.CallSub(mCallBack, mEventName & "_click", Array(BANano.ToElement(event.CurrentTarget).GetAttr("data-id")))   
End Sub

Alwaysbusy
Thanks, that worked really great!

Powerful stuff, thanks for this contribution!!

Walter
 
Upvote 0
Top