Skip to content

Latest commit

 

History

History
226 lines (158 loc) · 1020 KB

File metadata and controls

226 lines (158 loc) · 1020 KB

Dynamic Web Client (DWC) Implementation

As we've evolved Business BASIC over the years, it's always been important to preserve our customers’ technology investment while adding new features. Our approach with the DWC is no different. These notes start with a list of BBjControls and their DWC implementation, followed by some comments about new DWC-specific functionality.

DWC Implementation

DWC Window Dynamic Flow Layout

DWC Component Attributes

DWC Embedded Deployment

DWC Debugging

Sample: Responsive Client-Side Chart

Sample: Customer Master File Maintenance Program

BBjPWA

We'll start with the simple "hello" app from the BUI Getting Started page:

rem ' hello.txt sysgui = unt open (sysgui)"X0" bbjapi! = bbjapi() sysgui! = bbjapi!.getSysGui() window! = sysgui!.addWindow(100,100,275,75,"Hello",$00090083$,$$) window!.setCallback(bbjapi!.ON_CLOSE,"EOJ") hello! = window!.addButton(1,25,25,100,25,"Hello!") hello!.focus() hello!.setCallback(bbjapi!.ON_BUTTON_PUSH,"msgbox") goodbye! = window!.addButton(2,150,25,100,25,"Goodbye!") goodbye!.setCallback(bbjapi!.ON_BUTTON_PUSH,"eoj") process_events eoj: release msgbox: i = msgbox(info(1,4),64,fnmode$(info(3,6))) hello!.focus() return def fnmode$(mode$) if mode$="0" then return "Fat Client" if mode$="1" then return "Thin Client" if mode$="2" then return "Java Applet" if mode$="3" then return "Java Web Start" if mode$="4" then return "JavaBBjBridge" if mode$="5" then return "BUI (Browser)" if mode$="6" then return "DWC (Browser)" return mode$ fnend

This adds a test for INFO(3,6)="6", which identifies the DWC client.

By default, BBj DWC apps are published to URLs that look like this:

http://hostname:8888/webapp/appname

The default mapping to /webapp/ can be changed in Enterprise Manager, Web Context Configuration, Override Mappings.

Run the following program to publish hello.txt to http://localhost:8888/apps/hello (BUI) and

http://localhost:8888/webapp/hello (DWC):

rem ' publish.txt rem ' This assumes that the sample is on the desktop; adjust as necessary path$ = "" path$ = env("HOME",err=*next) + "/Desktop/" if path$="" then path$ = env("HOMEDRIVE") + env("HOMEPATH") + "/Desktop/" appname$ = "hello" source$ = "hello.txt" bbjHome! = System.getProperty("basis.BBjHome") config$ = bbjhome! + "/cfg/config.bbx" appServer! = bbjapi().getAdmin("admin","admin123").getWebAppServer() appConfig! = appServer!.makeEmptyAppConfig() appConfig!.setProgramName(path$ + source$) appConfig!.setConfigFile(config$) appConfig!.setWorkingDirectory(path$) appConfig!.setInterpreterUser(System.getProperty("user.name")) appConfig!.setBuiEnabled(1) appConfig!.setDwcEnabled(1) app! = appConfig!.buildApplication(appname$) published = appServer!.isPublished(app!) if !(published) then appServer!.publish(app!)

We can also publish this sample application interactively with Enterprise Manager:

The hello.txt program running in BUI and DWC:

DWC Implementation {#dwc-implementation}

BBjControl or subsystem Implementation status
BBjWebManager (BBjBuiManager) Complete.
BBjBusyIndicator DWC BBjBusyIndicator Complete.
BBjButton DWC BBjButton Complete.
BBjCEdit DWC BBjCEedit Complete.
BBjChart (BBjBarChart, BBjLineChart, BBjPieChart, BBjGenericChart) Complete. Like BUI, the DWC BBjChart is implemented with JFreeChart running on the server; it delivers generated static images to the client. For a responsive client-side charting component, see the Chart.js sample at the bottom of this document.
BBjCheckBox DWC BBjCheckBox Complete.
BBjChildWindow Complete Complete as of BBj 21.12 with the implementation of docked child windows (setDockLocation). If window creation flag $00100000$ is used, all x,y,w,h parameters for the window and controls on the window will be ignored; locations and sizes will be set dynamically. See "DWC Window Dynamic Flow Layout" below. In BBj 21.12+, if window creation flag $00008000$ is used, a simplified child window structure is created on the client that disables the status bar and docked child windows. This simpler window structure generates fewer HTML elements, and may be more efficient for some applications. In BBj 21.14+, if window creation flag $00001000$ is used, simple child windows are rendered as HTML <fieldset> elements, and the title string is rendered as a HTML <legend> element if the child window has a border. This produces an appearance like the BBjGroupBox control, but in a structure that works better with dynamic flow layout.
BBjClientFileSystem, BBjClientFile Complete. The browser security restrictions described in Interacting with client files in BUI and DWC apply to both browser clients.
BBjColorChooser DWC BBjColorChooser Complete as of BBj 21.13.
BBjDrawPanel Complete as of BBj 21.12.
BBjEditBox DWC BBjEditBox Complete.
BBjEditBoxSpinner DWC BBjEditBoxSpinner Complete.
BBjFileChooser DWC BBjFileChooser (fileopen) DWC BBjFileChooser (filesave) DWC BBjFileChooser (server) Complete Complete as of BBj 21.10 �Client-side and server-side FILEOPEN and FILESAVE See Interacting with client files in BUI and DWC. The server-side file chooser is Complete as of BBj 22.12. The client-side file chooser is Complete as of BBj 22. The server-side directory chooser is Complete as of BBj 22.
BBjFontChooser DWC BBjFontChooser Complete as of BBj 22.10.
BBjGeolocation Complete.
BBjGrid Basic support from BBj 22.10. �Supports a limited subset of the BBjGrid API. DWC proposes the BBjGridExWidget as a data grid optimized for use in the browser or encapsulating the BUI grid implementation in a BBjHtmlView control as a bridging step.
BBjGroupBox Complete.
BBjHtmlEdit (TinyMCE 6 or CKEditor 4) Complete.
BBjHtmlView Complete.
BBjImageCtrl Complete.
BBjInputD DWC BBjInputD Complete.
BBjInputDSpinner DWC BBjInputDSpinner Complete.
BBjInputE DWC BBjInputE Complete.
BBjInputESpinner DWC BBjInputESpinner Complete.
BBjInputN DWC BBjInputN Complete.
BBjInputNSpinner DWC BBjInputNSpinner Complete.
BBjInputT DWC BBjInputT Complete.
BBjInputTSpinner DWC BBjInputTSpinner Complete.
BBjListBox DWC BBjListBox Complete.
BBjListButton DWC BBjListButton Complete.
BBjListEdit DWC BBjListEdit Complete.
BBjMDI Complete as of BBj 22.10.
BBjMenuBar BBjMenu BBjMenuItem DWC BBjMenuBar DWC BBjMenuItem Complete.
BBjMenuButton DWC BBjMenuButton Complete.
BBjNavigator DWC BBjNavigator Complete.
BBjPopupMenu DWC BBjPopupMenu Complete.
BBjProgressBar DWC BBjProgressBar Complete.
BBjRadioButton DWC BBjRadioButton Complete.
BBjRadioGroup Complete.
BBjSlider DWC BBjSlider Complete.
BBjSplitter DWC BBjSplitter Complete as of BBj 22.11.
BBjStaticText Complete.
BBjStatusBar Complete.
BBjSysGui Complete.
BBjSystemMetrics Complete.
BBjTabCtrl DWC BBjTabCtrl Complete as of BBj 21.11.
BBjThinClient Complete.
BBjToolButton DWC BBjToolButton Complete.
BBjTopLevelWindow Complete. If window creation flag $00100000$ is used, all x,y,w,h parameters for the window and controls on the window will be ignored; locations and sizes will be set dynamically. See "DWC Window Dynamic Flow Layout" below.
BBjTree DWC BBjTree Complete as of BBj 21.12.
BBjWebComponent Complete (in progress) for BBj 24. Available for testing in BBj 23.04.
CLIENTENV function Complete, returning parameters from the URL query string.
Clipboard Complete; the clipisformat() and cliptostr() functions and the clipfromstr verb are implemented for text and image/png content, to the extent allowed by browsers. This is currently only reliable in Chromium-based browsers (Google Chrome & Microsoft Edge). See the sample program and notes in Bug 32814.
Console As of BBj 22, a basic console is available for debugging. Like the BUI mini console, this debug console is not for production deployment. See the DWC Debugging section below.
FILEOPEN and FILESAVE functions. Complete, including both server and client versions. See Interacting with client files in BUI and DWC. The clientfile.txt sample program at the bottom of that document works in GUI, BUI, and DWC.
Form validation DWC form validation Complete.
INFO functions INFO(3,6) returns "6" (BUI returns "5"). INFO(6,0) returns "WEB" (BUI returns "GWT"; GUI returns "SWING"). INFO(7,*) is implemented like BUI (see the clientenv() function).
Keyboard navigation DWC honors ENTER for button ID 1 and ESCAPE for button ID 2, but otherwise it leaves keyboard navigation up to the browser. BUI and GUI take complete control of keyboard events to implement BBx keyboard navigation rules.
MSGBOX function DWC MSGBOX Complete.
PROMPT function DWC PROMPT New function, implemented and Complete as of BBj 22.13.
SYSPRINT Complete. Like BUI, it prints to a server-side PDF, then delivers that completed PDF file to the browser (in a new browser tab) when the printer channel is closed.
User authentication DWC BBjUserAuthentication Complete.

GUI and/or BUI concepts that are not available in DWC:

  • BBjWrappedJComponent and ClientObject both depend on the assumption that the client is Java, so they can only be fully supported in the Java Swing GUI client. In BBj 24+, the BUI and DWC clients offer BBjWebComponent to integrate arbitrary web components as BBjControls. Also in BBj 24+, the BUI and DWC clients include placeholder implementations of BBjWrappedJComponent and ClientObject; in previous versions of BBj, applications report an error when accessing this functionality in BUI and DWC clients.
  • BBjScrollBar - DWC uses CSS to make Windows scrollable
  • BBjPrintPreview is currently implemented only in the Java Swing GUI client.

DWC Window Dynamic Flow Layout {#dwc-window-dynamic-flow-layout}

When the BBjTopLevelWindow or BBjChildWindow creation flag $00100000$ (automatically arranges all controls and child windows placed in the window to fit) is specified, all absolute positions (x,y) and absolute sizes (w,h) of controls added to that window are ignored. To see how this works in practice, run the sample master file maintenance program from the end of this document in DWC, select "Yes" for "Flow Layout?", and resize the window to see the controls flow to fit the window dimensions.

DWC Component Attributes {#dwc-component-attributes}

All DWC components define several attributes that can be used to adjust their behavior and appearance. The sample customer maintenance program at the end of this document demonstrates the "theme" attribute.

DWC Embedded Deployment {#dwc-embedded-deployment}

In BBj 21.13+, a published DWC app can be deployed as a custom web page saved to basis/htdocs/:

<!doctype html> <html lang="en"> <noscript>Embedded DWC webapp 'hello' is offline.</noscript> <meta charset="UTF-8"> <title>DWC Embedded Hello</title> <head> </head> <body> <div id="bbj-dwc"></div> <script type="text/javascript" src="/dwcembed/hello.js"></script> </body> </html>

The <script> line tells the /embeddwc/ servlet to start the DWC app named hello, adding it to the "bbj-dwc" div. This html file should be saved to the htdocs directory under the basis home directory; it can be accessed with a URL in the format http://localhost:8888/files/myhtml.html.

N.B.: DWC apps, like BUI apps, expect to be the only BASIS web app on a given web page. This deployment option can be used to add custom metadata to the <head> section of the web page; it can't be used to run multiple DWC apps on a single web page. To run multiple DWC apps on a single web page, they should be loaded from <iframe> elements that link to the standard DWC app URL.

DWC Debugging {#dwc-debugging}

When a BBx program encounters an untrapped error, it drops to a minimal debug console, where the developer can debug the problem and either restart or terminate the program.

rem ' hello.txt sysgui = unt open (sysgui)"X0" bbjapi! = bbjapi() sysgui! = bbjapi!.getSysGui() window! = sysgui!.addWindow(25,25,275,125,"Hello",$00090083$,$$) window!.setCallback(bbjapi!.ON_CLOSE,"eoj") hello! = window!.addButton(1,25,25,100,25,"Hello!") hello!.focus() hello!.setCallback(bbjapi!.ON_BUTTON_PUSH,"msgbox") goodbye! = window!.addButton(2,150,25,100,25,"Goodbye!") goodbye!.setCallback(bbjapi!.ON_BUTTON_PUSH,"eoj") console! = window!.addButton(3,25,75,100,25,"Console") console!.setCallback(bbjapi!.ON_BUTTON_PUSH,"console") rem print "Hello, world from hello.txt app." process_events eoj: release msgbox: i = msgbox(info(1,4),64,fnmode$(info(3,6))) hello!.focus() return def fnmode$(mode$) if mode$="0" then return "Fat Client" if mode$="1" then return "Thin Client" if mode$="2" then return "Java Applet" if mode$="3" then return "Java Web Start" if mode$="4" then return "JavaBBjBridge" if mode$="5" then return "BUI (Browser)" if mode$="6" then return "DWC (Browser)" return mode$ fnend console: escape return

Sample: Responsive Client-Side Chart {#sample:-responsive-client-side-chart}

The BBjChart control is based on the JFreeChart Java Swing component. Charts are rendered on the server and static images are delivered to the client. The alternative JavaScript-based chart sample below runs the Chart.js component in a BBjHtmlView control. It runs unchanged in the GUI, BUI, and DWC clients, and can be used in place of the BBjChart when client-side responsiveness is needed.

rem ' https://www.chartjs.org/ sysgui = unt open (sysgui)"X0" sysgui! = bbjapi().getSysGui() window! = sysgui!.addWindow(25,25,1200,750,"Chart.js",$00090083$) window!.setCallback(window!.ON_CLOSE,"eoj") window!.setCallback(window!.ON_RESIZE,"resize") html$ = "<html></head><body><canvas id='myChart'></canvas></body></html>" htmlview! = window!.addHtmlView(101,10,10,1180,730,html$,$$) htmlview!.setNoEdge(1) htmlview!.setCallback(htmlview!.ON_PAGE_LOADED,"page_loaded") htmlview!.setCallback(htmlview!.ON_SCRIPT_LOADED,"script_loaded") htmlview!.setCallback(htmlview!.ON_SCRIPT_FAILED,"script_failed") process_events eoj: release max: if window!.isMaximized() then window!.restore() else window!.maximize() endif return resize: event! = sysgui!.getLastEvent() htmlview!.setSize(event!.getWidth()-20,event!.getHeight()-20) return page_loaded: event! = sysgui!.getLastEvent() print date(0:"%Hz:%mz:%sz.%tz %p "),event!.getEventName()," ",event!.getUrl() htmlview!.clearCallback(htmlview!.ON_PAGE_LOADED) url$ = "https://cdn.jsdelivr.net/npm/chart.js@4.4.3/dist/chart.umd.min.js" url$ = "https://cdn.jsdelivr.net/npm/chart.js@latest/dist/chart.umd.min.js" htmlview!.injectUrl(url$,1) return script_loaded: event! = sysgui!.getLastEvent() msg$ = date(0:"%Hz:%mz:%sz.%tz %p ")+event!.getEventName()+" url="+str(event!.getUrl()) print msg$ rem ' https://www.chartjs.org/docs/latest/getting-started/ script$ = "" script$ = script$ + "var ctx = document.getElementById('myChart');" script$ = script$ + "var chart = new Chart(ctx, {" script$ = script$ + " type: 'bar'," script$ = script$ + " data: {" script$ = script$ + " labels: ['January', 'February', 'March', 'April', 'May', 'June', 'July']," script$ = script$ + " datasets: [{" script$ = script$ + " label: 'My First dataset'," script$ = script$ + " backgroundColor: 'rgb(255, 99, 132)'," rem script$ = script$ + " borderColor: 'rgb(255, 99, 132)'," script$ = script$ + " data: [0, 10, 5, 2, 20, 30, 45]" script$ = script$ + " }]" script$ = script$ + " }," script$ = script$ + " options: {}" script$ = script$ + "});" htmlview!.injectScript(script$,1) return script_failed: event! = sysgui!.getLastEvent() msg$ = date(0:"%Hz:%mz:%sz.%tz %p ")+event!.getEventName()+" url="+str(event!.getUrl()) print msg$ i = msgbox(msg$) return

The sample code running in the BBj GUI, BUI, and DWC clients on a Mac:

GUI:

BUI:

DWC:

Sample: Customer Master File Maintenance Program {#sample:-customer-master-file-maintenance-program}

rem ' Customer master file maintenance bbjapi().getConfig().releaseOnLostConnection(0) setesc ON_LOST_CONNECTION dim customer$:"id:c(6),name:c(32),phone:c(24)" filename$ = "customer.dat" customer = unt open (customer,err=makefile)filename$ goto init makefile: mkeyed filename$,[0:1:6],0,64 open (customer)filename$ while 1 dread customer.id$,customer.name$,customer.phone$,err=eof write record(customer)customer$ continue eof: break wend data "BASIS","BASIS International Ltd.","+1.505.345.5232" data "CHILE","Chile Company","+1.555.555.1212" init: sysgui = unt open (sysgui)"X0"; rem ' ALIAS X0 SYSGUI web! = bbjapi().getWebManager(err=skip) web!.setCallback(web!.ON_BROWSER_CLOSE,"ON_BROWSER_CLOSE",err=skip) skip: sysgui! = bbjapi().getSysGui() flags$ = $00090083$ if 0 and info(3,6)="6" then flags$ = iff(msgbox("Flow Layout?",4+32+256)=6,$00190083$,$00090083$) endif window! = sysgui!.addWindow(100,100,280,220,"Customers",flags$) window!.addStaticText(101,10,10,80,30,"ID:",$8000$) id! = window!.addEditBox(102,100,10,70,30,$$,$$) window!.addStaticText(103,10,50,80,30,"Name:",$8000$) name! = window!.addEditBox(104,100,50,170,30,$$,$$) window!.addStaticText(105,10,90,80,30,"Phone:",$8000$) phone! = window!.addEditBox(106,100,90,170,30,$$,$$) update! = window!.addButton(201,10,130,80,30,"Update",$$) delete! = window!.addButton(202,100,130,80,30,"Delete",$$) clear! = window!.addButton(203,190,130,80,30,"Clear",$$) themes$ = "danger"+$0a$+"default"+$0a$+"gray"+$0a$+"info" themes$ = themes$+$0a$+"primary"+$0a$+"success"+$0a$+"warning" theme! = window!.addListButton(204,10,180,125,250,themes$,$$) theme!.selectIndex(1) theme!.setCallback(theme!.ON_LIST_SELECT,"theme") appThemes$ = "light"+$0a$+"dark"+$0a$+"dark-pure"+$0a$+"system" appTheme! = window!.addListButton(205,145,180,125,250,appThemes$,$$) appTheme!.selectIndex(3) appTheme!.setCallback(theme!.ON_LIST_SELECT,"app_theme") id!.setText("BASIS") gosub fetch id!.setCallback(id!.ON_EDIT_MODIFY,"toggle") rem id!.setCallback(id!.ON_LOST_FOCUS,"fetch") update!.setCallback(update!.ON_BUTTON_PUSH,"update") delete!.setCallback(delete!.ON_BUTTON_PUSH,"remove") clear!.setCallback(clear!.ON_BUTTON_PUSH,"clear") window!.setCallback(window!.ON_CLOSE,"eoj") process_events eoj: release toggle: id$ = cvs(id!.getText(),7) update!.setEnabled(len(id$)) delete!.setEnabled(len(id$)) return fetch: id$ = pad(cvs(id!.getText(),7),6) if customer.id$ = id$ then return dim customer$:fattr(customer$) let customer.id$ = id$ read record(customer,key=customer.id$,dom=notfound)customer$ notfound: gosub display return update: customer.id$ = ctrl(sysgui,102,1) customer.name$ = ctrl(sysgui,104,1) customer.phone$ = ctrl(sysgui,106,1) write record (customer)customer$ i = msgbox("Customer "+customer.id$+" updated.",0,"Updated") gosub clear return remove: remove (customer,key=customer.id$,dom=nodelete) i = msgbox("Customer "+customer.id$+" deleted.",0,"Deleted") nodelete: gosub clear return clear: dim customer$:fattr(customer$) gosub display return display: id!.setText(cvs(customer.id$,3)) name!.setText(cvs(customer.name$,3)) phone!.setText(cvs(customer.phone$,3)) id!.focus() return theme: event! = sysgui!.getLastEvent() theme$ = event!.getSelectedItem() id!.setAttribute("theme",theme$) name!.setAttribute("theme",theme$) phone!.setAttribute("theme",theme$) update!.setAttribute("theme",theme$) delete!.setAttribute("theme",theme$) clear!.setAttribute("theme",theme$) theme!.setAttribute("theme",theme$) appTheme!.setAttribute("theme",theme$) return app_theme: event! = sysgui!.getLastEvent() appTheme$ = event!.getSelectedItem() web!.setTheme(appTheme$) return on_lost_connection: event$ = "ON_LOST_CONNECTION" goto terminate on_browser_close: event$ = "ON_BROWSER_CLOSE" goto terminate terminate: seterr release System.err.println(pgm(-2)+" "+event$+" "+str(tcb(19))) filename$ = System.getProperty("user.home") + "/Desktop/" + event$ + ".txt" u = unt open (u,mode="o_create,o_trunk")filename$ print (u)date(0:"%Hz:%mz:%sz.%tz ")+event$+" "+str(tcb(19)) close (u) release: release -1

This sample includes sample code to cleanly terminate the session if the user navigates away or abruptly closes the browser.

This sample application running in the BBj DWC client on a Mac shows the effect of theming (light theme, dark theme, and dark-pure theme with the controls set to the warning theme).



BBjPWA {#bbjpwa}

PWA, commonly known as a Progressive Web App, is a type of web application that works as both a web app and a mobile app on any device. The aim of PWA is to deliver a user experience similar to that of native apps.

Here's why PWAs are gaining popularity:

  • Responsive Design: PWAs adapt effortlessly to various screen sizes, ensuring a consistent user experience across desktop and mobile devices.
  • Native-Like Interactions: They mimic the behavior of native apps, providing smooth and intuitive interactions.
  • Secure: PWAs are served over the HTTPS protocol, ensuring data security and privacy.
  • Installable: Users can save PWAs to their home screens with corresponding icons for easy access, just like native apps.
  • Shareable: PWAs can be shared via a URL without the need for installation, making them easy to distribute and access.
  • Cross-Platform: PWAs can be published on different app stores, including Apple Store, Google Play, and Microsoft Store, expanding their reach to a wide audience.

BASIS provides BBjPWA which is a Node.js-based CLI (command line interface) tool designed to transform any DWC application into a Progressive Web App. This versatile tool offers the following capabilities:

  • Index Page: BBjPWA creates an index page, serving as an entry point for your DWC application, allowing it to function as an embedded DWC app.
  • Web App Manifest: It generates a manifest.json file, a crucial component for PWAs. This file ensures that your web app can be downloaded and presented to users similarly to a native app. The manifest.json file contains important information about your web application in JSON (JavaScript Object Notation) format.
  • Icons: You have the option to provide an icon for your app and BBjPWA will automatically generate icons in various sizes to ensure compatibility across different platforms.
  • Service Worker: BBjPWA sets up a service worker that caches your app's resources on the fly, including images, styles, and more. This enhances the app's performance, especially when users are offline.
  • Offline Page: BBjPWA can also generate an offline page that is displayed when users lose internet connectivity.

Note: BBjPWA is currently a work in progress, but it's already showing great promise!