Using the BBjAppLayout Plugin

The BBjAppLayout is a custom BBj widget for the DWC (Dynamic Web Client) that enables building common application layouts. The layout is responsive and automatically adjusts to fit different screen sizes.

It consists of a header, a footer, a collapsible drawer, and a content area. Each of these sections is, by default an instance of BBjChildWindow. Ususally, the header and footer are fixed, the drawer slides in and out of the viewport, and the content area is scrollable.

Note: A DWC application can have only one BBjAppLayout instance. Having multiple instances is not supported.

Note: The BBjTopLevelWindow should set the $00001000$ flag to fill the viewport.

Installation

Follow one of the steps to install BBjAppLayout.

Features

BBjAppLayout features:

  • Easy to set up.

  • Easy to customize.

  • Responsive.

  • Multiple layout options.

  • Compatible with DWC dark mode.

And much more !

The Gist

The following example demonstrates how to use the BBjAppLayout widget. Each part of the layout is an instance of BBjChildWindow, which can contain any valid BBjControl.

For the best results, the application should include a viewport meta tag that contains viewport-fit=cover. This meta tag ensures the viewport is scaled to fill the device display.

Example 1

web! = BBjAPI().getWebManager()
meta$ = "width=device-width, initial-scale=1.0, viewport-fit=cover, user-scalable=no"
web!.setMeta("viewport", meta$)

Note: The demos use the [dwc-icon-button] web component to create a drawer toggle button. The button includes the data-drawer-toggle attribute, which instructs BBjAppLayout to listen for click events from that component to toggle the drawer state.

Example 2

use ::BBjAppLayout/BBjAppLayout.bbj::BBjAppLayout

app! = new BBjAppLayout(wnd!)
header! = app!.getHeader()
toolbar! = header!.addChildWindow("", $00108800$, sysgui!.getAvailableContext())
toolbar!.addStyle("dwc-toolbar")

REM - Add a toggle button which works independently in the browser
toolbar!.addStaticText("
:<html>
: <dwc-icon-button name='menu-2' data-drawer-toggle>
: </dwc-icon-button>
:</html>")

toolbar!.addStaticText("<html><h3>DWC Application</h3></html>")

Example 3

style! = "
: body,html {overflow: hidden}
:
: .dwc-toolbar {
:    display: flex;
:    align-items: center;
:    gap: var(--dwc-space-m);
:    padding: 0 var(--dwc-space-m);
: }
:
: .dwc-logo {
:    display: flex;
:    align-items: center;
:    justify-content: center;
:    padding: var(--dwc-space-m) 0;
:    margin-bottom: var(--dwc-space-m);
:    border-bottom: thin solid var(--dwc-color-default)
: }
:
: .dwc-logo img {
:    max-width: 100px;
: }
:"

use ::BBjAppLayout/BBjAppLayout.bbj::BBjAppLayout

web! = BBjAPI().getWebManager()
rem The app should include a viewport meta tag which contains `viewport-fit=cover`, like the following.
rem This causes the viewport to be scaled to fill the device display.
web!.setMeta("viewport", "width=device-width, initial-scale=1.0, viewport-fit=cover, user-scalable=no")
web!.injectStyle(style!, 0)

sysgui! =  BBjAPI().openSysGui("X0")
imageManager! = sysgui!.getImageManager()

wnd! = sysgui!.addWindow("BBjAppLayout", $01001000$)
wnd!.setCallback(BBjAPI.ON_CLOSE,"eoj")

app! = new BBjAppLayout(wnd!)

rem Header
rem ==================
header! = app!.getHeader()
toolbar! = header!.addChildWindow("", $00108800$, sysgui!.getAvailableContext())
toolbar!.addStyle("dwc-toolbar")
toolbar!.addStaticText("<html><dwc-icon-button name='menu-2' data-drawer-toggle></dwc-icon-button></html>")
toolbar!.addStaticText("<html><h3>DWC Application</h3></html>")

rem Drawer
rem ==================
drawer! = app!.getDrawer()
drawer!.addStyle("dwc-drawer")

logo! = drawer!.addImageCtrl(imageManager!.loadImageFromFile("./assets/logo.png"))
logo!.addStyle("dwc-logo")

drawerMenu! = drawer!.addTabCtrl()
drawerMenu!.setCallback(drawerMenu!.ON_TAB_SELECT,"onPageChanged")
drawerMenu!.setAttribute("nobody","true")
drawerMenu!.setAttribute("borderless","true")
drawerMenu!.setAttribute("placement","left")
drawerMenu!.addTab("<dwc-icon name='dashboard'></dwc-icon>      Dashboard"    , -1)
drawerMenu!.addTab("<dwc-icon name='shopping-cart'></dwc-icon>  Orders"       , -1)
drawerMenu!.addTab("<dwc-icon name='users'></dwc-icon>          Customers"    , -1)
drawerMenu!.addTab("<dwc-icon name='box'></dwc-icon>            Products"     , -1)
drawerMenu!.addTab("<dwc-icon name='files'></dwc-icon>          Documents"    , -1)
drawerMenu!.addTab("<dwc-icon name='checklist'></dwc-icon>      Tasks"        , -1)
drawerMenu!.addTab("<dwc-icon name='chart-dots-2'></dwc-icon>   Analytics"    , -1)

rem Content
rem ==================
content! = app!.getContent()
content!.addStaticText("<html><h1>Application Title</h1></html>")
pageContent! = content!.addStaticText("<html><p>Content goes here</p></html>")

process_events

onPageChanged:
  event! = sysgui!.getLastEvent() 
  title$ = event!.getTitle().replaceAll("<[^>]*>","").trim()

  pageContent!.setText("<html><p>Content for " + title$ + " goes here</p></html>")
return

eoj:
release

Full Width Navbars

By default, BBjAppLayout renders the header and footer in off-screen mode. In this mode, the header and footer positions are shifted to fit beside the opened drawer. Disabling off-screen mode causes the header and footer to occupy the full available space, and shifts the drawer's top and bottom positions to align with the header and footer.

Example 4

use ::BBjAppLayout/BBjAppLayout.bbj::BBjAppLayout

app! = new BBjAppLayout(wnd!)
app!.setHeaderOffscreen(0)
app!.setFooterOffscreen(0)

Example 5

style! = "
: body,html {overflow: hidden}
:
: .dwc-toolbar {
:    display: flex;
:    align-items: center;
:    gap: var(--dwc-space-m);
:    padding: 0 var(--dwc-space-m);
: }
:
: .dwc-drawer {
:   padding-top: var(--dwc-space-m)    
: }
:
: .dwc-logo {
:    display: flex;
:    align-items: center;
:    justify-content: center;
: }
:
: .dwc-logo img {
:    height: 24px;
: }
:"

use ::BBjAppLayout/BBjAppLayout.bbj::BBjAppLayout

web! = BBjAPI().getWebManager()
rem The app should include a viewport meta tag which contains `viewport-fit=cover`, like the following.
rem This causes the viewport to be scaled to fill the device display.
web!.setMeta("viewport", "width=device-width, initial-scale=1.0, viewport-fit=cover, user-scalable=no")
web!.injectStyle(style!, 0)

sysgui! =  BBjAPI().openSysGui("X0")
imageManager! = sysgui!.getImageManager()

wnd! = sysgui!.addWindow("BBjAppLayout", $01001000$)
wnd!.setCallback(BBjAPI.ON_CLOSE,"eoj")

app! = new BBjAppLayout(wnd!)
app!.setHeaderOffscreen(0)

rem Header
rem ==================
header! = app!.getHeader()
toolbar! = header!.addChildWindow("", $00108800$, sysgui!.getAvailableContext())
toolbar!.addStyle("dwc-toolbar")
toolbar!.addStaticText("<html><dwc-icon-button name='menu-2' data-drawer-toggle></dwc-icon-button></html>")
logo! = toolbar!.addImageCtrl(imageManager!.loadImageFromFile("./assets/logo.png"))
logo!.addStyle("dwc-logo")
toolbar!.addStaticText("<html><h3>DWC Application</h3></html>")

rem Drawer
rem ==================
drawer! = app!.getDrawer()
drawer!.addStyle("dwc-drawer")

drawerMenu! = drawer!.addTabCtrl()
drawerMenu!.setCallback(drawerMenu!.ON_TAB_SELECT,"onPageChanged")
drawerMenu!.setAttribute("nobody","true")
drawerMenu!.setAttribute("borderless","true")
drawerMenu!.setAttribute("placement","left")
drawerMenu!.addTab("<dwc-icon name='dashboard'></dwc-icon>      Dashboard"    , -1)
drawerMenu!.addTab("<dwc-icon name='shopping-cart'></dwc-icon>  Orders"       , -1)
drawerMenu!.addTab("<dwc-icon name='users'></dwc-icon>          Customers"    , -1)
drawerMenu!.addTab("<dwc-icon name='box'></dwc-icon>            Products"     , -1)
drawerMenu!.addTab("<dwc-icon name='files'></dwc-icon>          Documents"    , -1)
drawerMenu!.addTab("<dwc-icon name='checklist'></dwc-icon>      Tasks"        , -1)
drawerMenu!.addTab("<dwc-icon name='chart-dots-2'></dwc-icon>   Analytics"    , -1)

rem Content
rem ==================
content! = app!.getContent()
content!.addStaticText("<html><h1>Application Title</h1></html>")
pageContent! = content!.addStaticText("<html><p>Content goes here</p></html>")

process_events

onPageChanged:
  event! = sysgui!.getLastEvent() 
  title$ = event!.getTitle().replaceAll("<[^>]*>","").trim()

  pageContent!.setText("<html><p>Content for " + title$ + " goes here</p></html>")
return

eoj:
release

Multiple Toolbars

The navbar has no limit on the number of toolbars you can add. Each toolbar is simply a BBjChildWindow.

The following demo shows how to use two toolbars. The first toolbar contains the drawer toggle button and the application title. The second toolbar includes a secondary navigation menu.

Note: For applications that require multilevel or hierarchical navigation, use the drawer to contain the primary navigation items, and place secondary navigation in a second toolbar within the navbar.

Example 6

use ::BBjAppLayout/BBjAppLayout.bbj::BBjAppLayout

app! = new BBjAppLayout(wnd!)
header! = app!.getHeader()
toolbar! = header!.addChildWindow("", $00108800$, sysgui!.getAvailableContext())
toolbar!.addStyle("dwc-toolbar")

toolbar!.addStaticText("
:<html>
: <dwc-icon-button name='menu-2' data-drawer-toggle>
: </dwc-icon-button>
:</html>")

toolbar!.addStaticText("<html><h3>DWC Application</h3></html>")

secondToolbar! = header!.addChildWindow("", $00108800$, sysgui!.getAvailableContext())
empty! = secondToolbar!.addChildWindow("", $00000810$, sysgui!.getAvailableContext())

menu! = secondToolbar!.addTabCtrl()
menu!.setAttribute("nobody","true")
menu!.setAttribute("borderless","true")
menu!.addTab("<dwc-icon name='report-money'></dwc-icon> Sales", empty!)
menu!.addTab("<dwc-icon name='building'></dwc-icon> Enterprise", empty!)
menu!.addTab("<dwc-icon name='credit-card'></dwc-icon> Payments", empty!)
menu!.addTab("<dwc-icon name='history'></dwc-icon> History", empty!)

Example 7

style! = "
: body,html {overflow: hidden}
:
: .dwc-toolbar {
:    display: flex;
:    align-items: center;
:    gap: var(--dwc-space-m);
:    padding: 0 var(--dwc-space-m);
: }
:
: .dwc-logo {
:    display: flex;
:    align-items: center;
:    justify-content: center;
:    padding: var(--dwc-space-m) 0;
:    margin-bottom: var(--dwc-space-m);
:    border-bottom: thin solid var(--dwc-color-default)
: }
:
: .dwc-logo img {
:    max-width: 100px;
: }
:"

use ::BBjAppLayout/BBjAppLayout.bbj::BBjAppLayout

web! = BBjAPI().getWebManager()
rem The app should include a viewport meta tag which contains `viewport-fit=cover`, like the following.
rem This causes the viewport to be scaled to fill the device display.
web!.setMeta("viewport", "width=device-width, initial-scale=1.0, viewport-fit=cover, user-scalable=no")
web!.injectStyle(style!, 0)

sysgui! =  BBjAPI().openSysGui("X0")
imageManager! = sysgui!.getImageManager()

wnd! = sysgui!.addWindow("BBjAppLayout", $01001000$)
wnd!.setCallback(BBjAPI.ON_CLOSE,"eoj")

app! = new BBjAppLayout(wnd!)

rem Header
rem ==================
header! = app!.getHeader()
firstToolbar! = header!.addChildWindow("", $00108800$, sysgui!.getAvailableContext())
firstToolbar!.addStyle("dwc-toolbar")
firstToolbar!.addStaticText("<html><dwc-icon-button name='menu-2' data-drawer-toggle></dwc-icon-button></html>")
firstToolbar!.addStaticText("<html><h3>DWC Application</h3></html>")

secondToolbar! = header!.addChildWindow("", $00108800$, sysgui!.getAvailableContext())
secondToolbarMenu! = secondToolbar!.addTabCtrl()
secondToolbarMenu!.setAttribute("nobody","true")
secondToolbarMenu!.setAttribute("borderless","true")
secondToolbarMenu!.addTab("<dwc-icon name='report-money'></dwc-icon>  Sales"        , -1)
secondToolbarMenu!.addTab("<dwc-icon name='building'></dwc-icon>      Enterprise"   , -1)
secondToolbarMenu!.addTab("<dwc-icon name='credit-card'></dwc-icon>   Payments"     , -1)
secondToolbarMenu!.addTab("<dwc-icon name='history'></dwc-icon>       History"      , -1)

rem Drawer
rem ==================
drawer! = app!.getDrawer()
drawer!.addStyle("dwc-drawer")

logo! = drawer!.addImageCtrl(imageManager!.loadImageFromFile("./assets/logo.png"))
logo!.addStyle("dwc-logo")

drawerMenu! = drawer!.addTabCtrl()
drawerMenu!.setCallback(drawerMenu!.ON_TAB_SELECT, "onPageChanged")
drawerMenu!.setAttribute("nobody","true")
drawerMenu!.setAttribute("borderless","true")
drawerMenu!.setAttribute("placement","left")
drawerMenu!.addTab("<dwc-icon name='dashboard'></dwc-icon>      Dashboard"    , -1)
drawerMenu!.addTab("<dwc-icon name='shopping-cart'></dwc-icon>  Orders"       , -1)
drawerMenu!.addTab("<dwc-icon name='users'></dwc-icon>          Customers"    , -1)
drawerMenu!.addTab("<dwc-icon name='box'></dwc-icon>            Products"     , -1)
drawerMenu!.addTab("<dwc-icon name='files'></dwc-icon>          Documents"    , -1)
drawerMenu!.addTab("<dwc-icon name='checklist'></dwc-icon>      Tasks"        , -1)
drawerMenu!.addTab("<dwc-icon name='chart-dots-2'></dwc-icon>   Analytics"    , -1)

rem Content
rem ==================
content! = app!.getContent()
content!.addStaticText("<html><h1>Application Title</h1></html>")
pageContent! = content!.addStaticText("<html><p>Content goes here</p></html>")

process_events

onPageChanged:
  event! = sysgui!.getLastEvent() 
  title$ = event!.getTitle().replaceAll("<[^>]*>","").trim()

  pageContent!.setText("<html><p>Content for " + title$ + " goes here</p></html>")
return

eoj:
release

Sticky Toolbars

A sticky toolbar is a toolbar that remains visible at the top of the page when the user scrolls down, while the navbar height collapses to maximize space for the page content. This type of toolbar typically contains a fixed navigation menu relevant to the current page.

Sticky toolbars can be created using the CSS custom property --dwc-app-layout-header-collapse-height and the BBjAppLayout::HeaderReveal option.

When the BBjAppLayout::HeaderReveal option is set to true, the header is initially visible on render, then hidden when the user scrolls down. It reappears when the user scrolls back up.

The CSS custom property --dwc-app-layout-header-collapse-height controls how much of the header navbar is hidden.

Example 8

style! = "
: body,html {overflow: hidden}
:
: :root {   
:   --dwc-app-layout-header-collapse-height: 45px;
: }
:
: .dwc-toolbar {
:    display: flex;
:    align-items: center;
:    gap: var(--dwc-space-m);
:    padding: 0 var(--dwc-space-m);
: }
:
: .dwc-logo {
:    display: flex;
:    align-items: center;
:    justify-content: center;
:    padding: var(--dwc-space-m) 0;
:    margin-bottom: var(--dwc-space-m);
:    border-bottom: thin solid var(--dwc-color-default)
: }
:
: .dwc-logo img {
:    max-width: 100px;
: }
: 
: .dwc-content {
:   max-width: 600px;
:   margin: 0 auto; 
: }
:
: .dwc-card {
:    padding: var(--dwc-space-m);
:    margin: var(--dwc-space-m) 0;
:    border: thin solid var(--dwc-color-default);
:    border-radius: var(--dwc-border-radius-m);
:    background-color: var(--dwc-surface-3);
: }
:"

use ::BBjAppLayout/BBjAppLayout.bbj::BBjAppLayout

web! = BBjAPI().getWebManager()
rem The app should include a viewport meta tag which contains `viewport-fit=cover`, like the following.
rem This causes the viewport to be scaled to fill the device display.
web!.setMeta("viewport", "width=device-width, initial-scale=1.0, viewport-fit=cover, user-scalable=no")
web!.injectStyle(style!, 0)

sysgui! =  BBjAPI().openSysGui("X0")
imageManager! = sysgui!.getImageManager()

wnd! = sysgui!.addWindow("BBjAppLayout", $01001000$)
wnd!.setCallback(BBjAPI.ON_CLOSE,"eoj")

app! = new BBjAppLayout(wnd!)
app!.setHeaderReveal(1)

rem Header
rem ==================
header! = app!.getHeader()
firstToolbar! = header!.addChildWindow("", $00108800$, sysgui!.getAvailableContext())
firstToolbar!.addStyle("dwc-toolbar")
firstToolbar!.addStaticText("<html><dwc-icon-button name='menu-2' data-drawer-toggle></dwc-icon-button></html>")
firstToolbar!.addStaticText("<html><h3>DWC Application</h3></html>")

secondToolbar! = header!.addChildWindow("", $00108800$, sysgui!.getAvailableContext())
secondToolbarMenu! = secondToolbar!.addTabCtrl()
secondToolbarMenu!.setAttribute("nobody","true")
secondToolbarMenu!.setAttribute("borderless","true")
secondToolbarMenu!.addTab("<dwc-icon name='report-money'></dwc-icon>  Sales"        , -1)
secondToolbarMenu!.addTab("<dwc-icon name='building'></dwc-icon>      Enterprise"   , -1)
secondToolbarMenu!.addTab("<dwc-icon name='credit-card'></dwc-icon>   Payments"     , -1)
secondToolbarMenu!.addTab("<dwc-icon name='history'></dwc-icon>       History"      , -1)

rem Drawer
rem ==================
drawer! = app!.getDrawer()
drawer!.addStyle("dwc-drawer")

logo! = drawer!.addImageCtrl(imageManager!.loadImageFromFile("./assets/logo.png"))
logo!.addStyle("dwc-logo")

drawerMenu! = drawer!.addTabCtrl()
drawerMenu!.setCallback(drawerMenu!.ON_TAB_SELECT,"onPageChanged")
drawerMenu!.setAttribute("nobody","true")
drawerMenu!.setAttribute("borderless","true")
drawerMenu!.setAttribute("placement","left")
drawerMenu!.addTab("<dwc-icon name='dashboard'></dwc-icon>      Dashboard"    , -1)
drawerMenu!.addTab("<dwc-icon name='shopping-cart'></dwc-icon>  Orders"       , -1)
drawerMenu!.addTab("<dwc-icon name='users'></dwc-icon>          Customers"    , -1)
drawerMenu!.addTab("<dwc-icon name='box'></dwc-icon>            Products"     , -1)
drawerMenu!.addTab("<dwc-icon name='files'></dwc-icon>          Documents"    , -1)
drawerMenu!.addTab("<dwc-icon name='checklist'></dwc-icon>      Tasks"        , -1)
drawerMenu!.addTab("<dwc-icon name='chart-dots-2'></dwc-icon>   Analytics"    , -1)

rem Content
rem ==================
content! = app!.getContent()
content!.addStyle("dwc-content")
pageHeader! = content!.addStaticText("<html><h1>Application Title</h1></html>")

lorem! = "
:Lorem Ipsum is simply dummy text of the printing and typesetting 
:industry. Lorem Ipsum has been the industry's standard dummy 
:text ever since the 1500s when an unknown printer took a galley 
:of type and scrambled it to make a type specimen book. It has 
:survived not only five centuries, but also the leap into electronic 
:typesetting, remaining essentially unchanged. It was popularised 
:in the 1960s with the release of Letraset sheets containing Lorem 
:Ipsum passages, and more recently with desktop publishing software
: like Aldus PageMaker including versions of Lorem Ipsum.
:"

for i=1 to 10
    card! = content!.addChildWindow("", $00108000$, sysgui!.getAvailableContext())
    card!.addStyle("dwc-card")
    cardTitle! = card!.addStaticText("<html><h2>What is Lorem Ipsum (" + str(i) +")?</h2></html>")
    cardContent! = card!.addStaticText("<html><p>" + lorem! + "</p></html>")
next 

process_events

onPageChanged:
  event! = sysgui!.getLastEvent() 
  title$ = event!.getTitle().replaceAll("<[^>]*>","").trim()

  pageHeader!.setText("<html><h1>" + title$ + " Page</h1></html>")
return

eoj:
release

Mobile Navigation Layout

The bottom navbar can be used to provide an alternative navigation experience at the bottom of the application. This type of navigation is especially common in mobile apps.

Note: Notice how the drawer is hidden in the following demo. The BBjAppLayout widget supports three drawer positions: DRAWER_PLACEMENT_LEFT, DRAWER_PLACEMENT_RIGHT, and DRAWER_PLACEMENT_HIDDEN.

Example 9

style! = "
: body,html {overflow: hidden}
:
: .dwc-toolbar {
:    display: flex;
:    align-items: center;
:    gap: var(--dwc-space-m);
:    padding: 0 var(--dwc-space-m);
: }
:
: .dwc-logo {
:    display: flex;
:    align-items: center;
:    justify-content: center;
: }
:
: .dwc-logo img {
:    height: 24px;
: }
:
: @media (max-width: 600px) {  
:  dwc-tab::part(title) { 
:     display: none;
:   }    
: }
:"

use ::BBjAppLayout/BBjAppLayout.bbj::BBjAppLayout

web! = BBjAPI().getWebManager()
rem The app should include a viewport meta tag which contains `viewport-fit=cover`, like the following.
rem This causes the viewport to be scaled to fill the device display.
web!.setMeta("viewport", "width=device-width, initial-scale=1.0, viewport-fit=cover, user-scalable=no")
web!.injectStyle(style!, 0)

sysgui! =  BBjAPI().openSysGui("X0")
imageManager! = sysgui!.getImageManager()

wnd! = sysgui!.addWindow("BBjAppLayout", $01001000$)
wnd!.setCallback(BBjAPI.ON_CLOSE,"eoj")

app! = new BBjAppLayout(wnd!)
app!.setHeaderOffscreen(0)
app!.setDrawerPlacement(BBjAppLayout.DRAWER_PLACEMENT_HIDDEN)

rem Header
rem ==================
header! = app!.getHeader()
toolbar! = header!.addChildWindow("", $00108800$, sysgui!.getAvailableContext())
toolbar!.addStyle("dwc-toolbar")
logo! = toolbar!.addImageCtrl(imageManager!.loadImageFromFile("./assets/logo.png"))
logo!.addStyle("dwc-logo")
toolbar!.addStaticText("<html><h3>DWC Application</h3></html>")

rem Footer
rem ==================
footer! = app!.getFooter()
footer!.addStyle("dwc-footer")

footerMenu! = footer!.addTabCtrl()
footerMenu!.setCallback(footerMenu!.ON_TAB_SELECT, "onPageChanged")
footerMenu!.setAttribute("nobody","true")
footerMenu!.setAttribute("borderless","true")
footerMenu!.setAttribute("placement","bottom")
footerMenu!.setAttribute("alignment","stretch")
footerMenu!.addTab("<dwc-icon expanse='s' name='dashboard'></dwc-icon>      <span part='title'>Dashboard</span>"    , -1)
footerMenu!.addTab("<dwc-icon expanse='s' name='shopping-cart'></dwc-icon>  <span part='title'>Orders</span>"       , -1)
footerMenu!.addTab("<dwc-icon expanse='s' name='users'></dwc-icon>          <span part='title'>Customers</span>"    , -1)
footerMenu!.addTab("<dwc-icon expanse='s' name='box'></dwc-icon>            <span part='title'>Products</span>"     , -1)
footerMenu!.addTab("<dwc-icon expanse='s' name='files'></dwc-icon>          <span part='title'>Documents</span>"    , -1)

rem Content
rem ==================
content! = app!.getContent()
content!.addStaticText("<html><h1>Application Title</h1></html>")
pageContent! = content!.addStaticText("<html><p>Content goes here</p></html>")

process_events

onPageChanged:
  event! = sysgui!.getLastEvent() 
  title$ = event!.getTitle().replaceAll("<[^>]*>","").trim()

  pageContent!.setText("<html><p>Content for " + title$ + " goes here</p></html>")
return

eoj:
release

Footer Reveal

Just like BBjAppLayout::HeaderReveal, BBjAppLayout::FooterReveal is also supported. When the FooterReveal option is set to true, the footer is initially visible on render, then hidden when the user scrolls up. It reappears once the user scrolls down again.

Example 10

style! = "
: body,html {overflow: hidden}
:
: .dwc-toolbar {
:    display: flex;
:    align-items: center;
:    gap: var(--dwc-space-m);
:    padding: 0 var(--dwc-space-m);
:    background-color: var(--dwc-color-primary);
:    color: var(--dwc-color-on-primary-text);
: }
:
: .dwc-logo {
:    display: flex;
:    align-items: center;
:    justify-content: center;
: }
:
: .dwc-logo img {
:    height: 24px;
: }
:
: .dwc-content {
:   max-width: 600px;
:   margin: 0 auto; 
: }
:    
: .dwc-card {
:    padding: var(--dwc-space-m);
:    margin: var(--dwc-space-m) 0;
:    border: thin solid var(--dwc-color-default);
:    border-radius: var(--dwc-border-radius-m);
:    background-color: var(--dwc-surface-3);
: }
:    
: @media (max-width: 600px) {  
:  dwc-tab::part(title) { 
:     display: none;
:   }    
: }
:"

use ::BBjAppLayout/BBjAppLayout.bbj::BBjAppLayout

web! = BBjAPI().getWebManager()
rem The app should include a viewport meta tag which contains `viewport-fit=cover`, like the following.
rem This causes the viewport to be scaled to fill the device display.
web!.setMeta("viewport", "width=device-width, initial-scale=1.0, viewport-fit=cover, user-scalable=no")
web!.injectStyle(style!, 0)

sysgui! =  BBjAPI().openSysGui("X0")
imageManager! = sysgui!.getImageManager()

wnd! = sysgui!.addWindow("BBjAppLayout", $01001000$)
wnd!.setCallback(BBjAPI.ON_CLOSE,"eoj")

app! = new BBjAppLayout(wnd!)
app!.setFooterReveal(1)
app!.setFooterShadow(BBjAppLayout.SHADOW_SCROLL)
app!.setDrawerPlacement(BBjAppLayout.DRAWER_PLACEMENT_HIDDEN)

rem Header
rem ==================
header! = app!.getHeader()
toolbar! = header!.addChildWindow("", $00108800$, sysgui!.getAvailableContext())
toolbar!.addStyle("dwc-toolbar")
logo! = toolbar!.addImageCtrl(imageManager!.loadImageFromFile("./assets/logo.png"))
logo!.addStyle("dwc-logo")
toolbar!.addStaticText("<html><h3>DWC Application</h3></html>")

rem Footer
rem ==================
footer! = app!.getFooter()
footer!.addStyle("dwc-footer")

footerMenu! = footer!.addTabCtrl()
footerMenu!.setCallback(footerMenu!.ON_TAB_SELECT, "onPageChanged")
footerMenu!.setAttribute("nobody","true")
footerMenu!.setAttribute("borderless","true")
footerMenu!.setAttribute("placement","bottom")
footerMenu!.setAttribute("alignment","stretch")
footerMenu!.addTab("<dwc-icon expanse='s' name='dashboard'></dwc-icon>      <span part='title'>Dashboard</span>"    , -1)
footerMenu!.addTab("<dwc-icon expanse='s' name='shopping-cart'></dwc-icon>  <span part='title'>Orders</span>"       , -1)
footerMenu!.addTab("<dwc-icon expanse='s' name='users'></dwc-icon>          <span part='title'>Customers</span>"    , -1)
footerMenu!.addTab("<dwc-icon expanse='s' name='box'></dwc-icon>            <span part='title'>Products</span>"     , -1)
footerMenu!.addTab("<dwc-icon expanse='s' name='files'></dwc-icon>          <span part='title'>Documents</span>"    , -1)

rem Content
rem ==================
content! = app!.getContent()
content!.addStyle("dwc-content")
pageHeader! = content!.addStaticText("<html><h1>Application Title</h1></html>")

lorem! = "
:Lorem Ipsum is simply dummy text of the printing and typesetting 
:industry. Lorem Ipsum has been the industry's standard dummy 
:text ever since the 1500s when an unknown printer took a galley 
:of type and scrambled it to make a type specimen book. It has 
:survived not only five centuries, but also the leap into electronic 
:typesetting, remaining essentially unchanged. It was popularised 
:in the 1960s with the release of Letraset sheets containing Lorem 
:Ipsum passages, and more recently with desktop publishing software
: like Aldus PageMaker including versions of Lorem Ipsum.
:"

for i=1 to 10
    card! = content!.addChildWindow("", $00108000$, sysgui!.getAvailableContext())
    card!.addStyle("dwc-card")
    cardTitle! = card!.addStaticText("<html><h2>What is Lorem Ipsum (" + str(i) +")?</h2></html>")
    cardContent! = card!.addStaticText("<html><p>" + lorem! + "</p></html>")
next 

process_events

onPageChanged:
  event! = sysgui!.getLastEvent() 
  title$ = event!.getTitle().replaceAll("<[^>]*>","").trim()

  pageHeader!.setText("<html><h1>" + title$ + " Page</h1></html>")
return


eoj:
release

Drawer Breakpoint

By default, when the screen width is 800px or less, the drawer switches to popover mode. This is known as breakpoint. In popover mode, the drawer displays over the content area with an overlay. The breakpoint can be configured using the BBjAppLayout::setDrawerBreakpoint method, and it must be a valid media query.

For instance, in the following example, the drawer breakpoint is configured to 500px or less.

Example 11

use ::BBjAppLayout/BBjAppLayout.bbj::BBjAppLayout

app! = new BBjAppLayout(wnd!)
app!.setDrawerBreakpoint("(max-width: 500px)")

Events

The BBjAppLayout supports three events:

  • ON_DRAWER_OPENED: Fired when the drawer is opened.

  • ON_DRAWER_CLOSED: Fired when the drawer is closed.

  • ON_DRAWER_TOGGLED: Fired when the drawer is toggled.

The event's payload is an instance of the ::BBjAppLayout/BBjAppLayout.bbj::BBjAppLayoutEvent

Example 12

use ::BBjAppLayout/BBjAppLayout.bbj::BBjAppLayout
use ::BBjAppLayout/BBjAppLayout.bbj::BBjAppLayoutEvent

app! = new BBjAppLayout(wnd!)
app!.setCallback(BBjAppLayout.ON_DRAWER_TOGGLED, "onDrawerToggled")

onDrawerToggled:
  declare auto BBjAppLayoutEvent payload!
  event! = sysgui!.getLastEvent()
  payload! = event!.getObject()

  let x = MSGBOX("Is Opened = " + str(payload!.isDrawerOpened()), 0, "Drawer Toggled")
return

See Also

AppLayout Plugin Overview