B4J Question [BANano] Collapse Skeleton views

angel_

Well-Known Member
Licensed User
Longtime User
I have a form with many views, is there a way to extend/collapse the views?
 
Solution
NOTE: see https://www.b4x.com/android/forum/threads/banano-exskcollapsable-inside-sktabs.143898/ for a new version that does not use a checkbox/radio input and works better together with other BANanoskeleton components!

This is a great example of the power of BANano. Without writing a single line of Javascript and just by using common B4X syntax with some CSS magic, we can write such a 'collapsable' component quite easily. As this is not a wrap of another Javascript/css library, it is very lightweight again as it does not have this extra layer to carry and totally customizable.

New component EXSKCollapsable class (I gave it the extra prefix EX as it it not part of the core BANanoskeleton library):
B4X:
'Custom BANano View...

alwaysbusy

Expert
Licensed User
Longtime User
NOTE: see https://www.b4x.com/android/forum/threads/banano-exskcollapsable-inside-sktabs.143898/ for a new version that does not use a checkbox/radio input and works better together with other BANanoskeleton components!

This is a great example of the power of BANano. Without writing a single line of Javascript and just by using common B4X syntax with some CSS magic, we can write such a 'collapsable' component quite easily. As this is not a wrap of another Javascript/css library, it is very lightweight again as it does not have this extra layer to carry and totally customizable.

New component EXSKCollapsable class (I gave it the extra prefix EX as it it not part of the core BANanoskeleton library):
B4X:
'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 (panelID As string)
'#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: OnlyOne, DisplayName: Only one open, FieldType: Boolean, DefaultValue: false, Description: Only allow one panel open at the same time
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 = ""
    Private mOnlyOne As Boolean = False
End Sub

Public Sub Initialize (CallBack As Object, Name As String, EventName As String)
    mName = Name
    mEventName = EventName.ToLowerCase
    mCallBack = CallBack
End Sub

' returns the BANanoElement
public Sub getElement() As BANanoElement
    Return mElement
End Sub

' returns the tag id
public Sub getID() As String
    Return mName
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")
  
        mOnlyOne = Props.Get("OnlyOne")
    End If
 
    Dim exStyle As String = BuildExStyle
 
    mElement = mTarget.Append($"<div id="${mName}" class="skcoll-tabs ${mClasses}" style="${exStyle}${mStyle}">"$).Get("#" & mName)
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

public Sub setStyle(Style As String)
    If mElement <> Null Then
        mElement.SetStyle(Style)
    End If
    mStyle = Style
End Sub

public Sub getStyle() As String
    Return mStyle
End Sub
#End Region

' add a new panel. Then use GetHeader and GetContent to fill them
public Sub AddPanel(panelID As String, panelStyle As String, HeaderStyle As String, ContentStyle As String)
    Dim TypeCollapse As String = "checkbox"
    Dim forRadio As String = ""
    If mOnlyOne Then
        TypeCollapse = "radio"
        forRadio = $" name="${mName}" "$
    End If
    mElement.Append($"[BANCLEAN]
        <div id="${mName}${panelID}" class="skcoll-tab" style="${panelStyle}">
            <input type="${TypeCollapse}" ${forRadio} id="${mName}${panelID}chk" class="skcoll-input">
            <label id="${mName}${panelID}header" data-panelid="${panelID}" class="skcoll-tab-header" for="${mName}${panelID}chk" style="${HeaderStyle}"></label>
            <div id="${mName}${panelID}content" class="skcoll-tab-content"  style="${ContentStyle}">
        </div>"$)
    Dim mElementHeader As BANanoElement
    mElementHeader.Initialize("#" & mName & panelID & "header")
    Dim event As BANanoEvent
    mElementHeader.AddEventListener("click", BANano.CallBack(Me, "HandleClick", event),True)
End Sub

public Sub GetHeader(PanelID As String) As BANanoElement
    Dim tmpElem As BANanoElement
    tmpElem.Initialize("#" & mName & PanelID & "header")
    Return tmpElem
End Sub

public Sub GetContent(PanelID As String) As BANanoElement
    Dim tmpElem As BANanoElement
    tmpElem.Initialize("#" & mName & PanelID & "content")
    Return tmpElem
End Sub

public Sub setOnlyOneOpen(bool As Boolean)
    mOnlyOne = bool
End Sub

public Sub CloseAllPanels()  
    Dim inputs() As BANanoElement = mElement.Find("input")  
    For i = 0 To inputs.Length - 1
        If inputs(i).GetField("checked").result = True Then
            inputs(i).SetField("checked", False)
        End If
    Next
End Sub

public Sub OpenPanel(panelId As String)
    Dim input As BANanoElement
    input.Initialize("#" & mName & panelId & "chk")
    input.SetField("checked", True)
End Sub

#Region Internal Events
private Sub HandleClick(event As BANanoEvent) 'ignore
    Dim tmpElem As BANanoElement
    tmpElem.Initialize(event.Target)
    Dim panelID As String = tmpElem.GetData("panelid")
    If panelID = Null Then
        Dim elems() As BANanoElement = tmpElem.Closest("label")
        If elems.Length > 0 Then
            panelID = elems(0).GetData("panelid")
        End If
    End If
    BANano.CallSub(mCallBack, mEventName & "_click", Array(panelID))
End Sub
#End Region

#if CSS
.skcoll-input {
  position: absolute;
  opacity: 0;
  z-index: -1;
}
.skcoll-tabs {
  overflow: hidden;
}
.skcoll-tab {
  width: 100%;
  overflow: hidden;
}
.skcoll-tab-header {
  display: flex;
  justify-content: space-between;
  padding: 1em;
  cursor: pointer;
}
.skcoll-tab-header::after {
  content: "❯";
  width: 1em;
  height: 1em;
  text-align: center;
  transition: all 0.35s;
}
.skcoll-tab-content {
  max-height: 0;
  padding: 0 1em;
  transition: all 0.35s;
}
.skcoll-input:checked + .skcoll-tab-header::after {
  transform: rotate(90deg);
}
.skcoll-input:checked ~ .skcoll-tab-content {
  max-height: 100vh;
  padding: 1em;
}
#End If

Usage:
Collapsable where you can open all panels at the same time (OnlyOneOpen = False):

1665132761886.png

B4X:
    Dim body As BANanoElement
    body.Initialize("#body")
 
    ' some styling
    Dim panelStyle As String = "margin-bottom: -8px; border: 1px solid gray"
    Dim headerStyle As String = "background: #2c3e50;color : white;font-weight: 400"
    Dim contentStyle As String = "background-color: #ecf0f1"
 
    ' initialize the new compoent and add it to the body
    Dim coll As EXSKCollapsable
    coll.Initialize(Me, "collapse", "collapse")
    coll.OnlyOneOpen = False ' <-----------------------------------
    coll.Style = "border-radius: 8px; box-shadow: 0 4px 4px -2px rgb(0 0 0 / 50%);margin: 1rem"
    coll.AddToParent(body.Name)

    ' add panels and fill the header and content
    coll.AddPanel("panel1", panelStyle, headerStyle, contentStyle)
    coll.GetHeader("panel1").Append($"<h5 style="margin-bottom: 0px">Item 1</h5>"$) ' add some html
    coll.GetContent("panel1").LoadLayout("myLayout") ' or load a complete layout

    coll.AddPanel("panel2", panelStyle, headerStyle, contentStyle)
    coll.GetHeader("panel2").Append($"<h5 style="margin-bottom: 0px">Item 2</h5>"$)
    coll.GetContent("panel2").LoadLayout("myLayout")

    coll.AddPanel("panel3", panelStyle, headerStyle, contentStyle)
    coll.GetHeader("panel3").Append($"<h5 style="margin-bottom: 0px">Item 3</h5>"$)
    coll.GetContent("panel3").LoadLayout("myLayout")

Collapsable where you can only open one panel at the same time (OnlyOneOpen = True):

1665132802714.png

B4X:
    Dim body As BANanoElement
    body.Initialize("#body")
 
    ' some styling
    Dim panelStyle As String = "margin-bottom: -8px; border: 1px solid gray"
    Dim headerStyle As String = "background: #2c3e50;color : white;font-weight: 400"
    Dim contentStyle As String = "background-color: #ecf0f1"
 
    ' initialize the new compoent and add it to the body
    Dim coll As EXSKCollapsable
    coll.Initialize(Me, "collapse", "collapse")
    coll.OnlyOneOpen = True ' <-----------------------------------
    coll.Style = "border-radius: 8px; box-shadow: 0 4px 4px -2px rgb(0 0 0 / 50%);margin: 1rem"
    coll.AddToParent(body.Name)
  
    ' add panels and fill the header and content
    coll.AddPanel("panel1", panelStyle, headerStyle, contentStyle)
    coll.GetHeader("panel1").Append($"<h5 style="margin-bottom: 0px">Item 1</h5>"$) ' add some html
    coll.GetContent("panel1").LoadLayout("myLayout") ' or load a complete layout

    coll.AddPanel("panel2", panelStyle, headerStyle, contentStyle)
    coll.GetHeader("panel2").Append($"<h5 style="margin-bottom: 0px">Item 2</h5>"$)
    coll.GetContent("panel2").LoadLayout("myLayout")

    coll.AddPanel("panel3", panelStyle, headerStyle, contentStyle)
    coll.GetHeader("panel3").Append($"<h5 style="margin-bottom: 0px">Item 3</h5>"$)
    coll.GetContent("panel3").LoadLayout("myLayout")

Some extra methods and events:
B4X:
Event: Click(panelID as String)
Methods:
OpenPanel(PanelID as string)
CloseAllPanels

Alwaysbusy
 
Last edited:
Upvote 0
Solution
Top