Dynamic WebClient Programming Peter van Dam
Peter van Dam • Progress fanatic since 1985 • Version 3-4-5-6-7-8-9 • CHUI-GUI-Batch-WebSpeed-WebClient • Founder of www.v9stuff.com • Author in Progressions & user group magazines • Founder of www.netsetup.nl
Dynamic WebClient Programming • Dynamic Windows, Frames & Widgets • Dynamic Browses • Dynamic Buffers & Queries • Dynamic Temp-Tables • Objects, Handles, Methods & Attributes • AppServers • WebClient
What is WebClient? WebClient = runtime – db connect
WebClient = runtime – db connect DB AppServer • Need AppServer to talk to a database • Separate User Interface from Business Logic
Internet orIntranet Run Progress over inter/intranet • Full GUI capabilities on end user PC • WebClient does NOT run in browser DB AppServer
Prepare your application • No database connects from the client • Port your application to AppServer • User Interface runs on client • Business Logic runs on server • Client issues AppServer calls • ADM II offers these capabilities
Window architecture • Built in ADM II • SDO, SmartDataBrowser, SmartDataViewer, SmartPanel • Run with AppServer • RowsToBatch=100 • Nothing fancy (we’ll get to that later)
Startup statistics • 198 KB of client-side r-code to start • 6 AppServer calls over internet • 117 KB of server-side r-code to start • 19 KB of data received • 100 rows cached on client
After 100 rows • 4 AppServer calls • 18 KB of data received • 200 rows cached on client • Response time 4 seconds on 28k8
Deployment • This screen compiles to 198 KB of client-side r-code • Remember, nothing fancy • Not a single bit of custom code • Imagine deploying an entire application…
Deploying an application (I) • Consider an average application with 100 screens • Some screens are simpler • Many screens are more complex • Some screens are much more complex • Some objects are being reused
Deploying an application (II) • A conservative guess: average screen size 250 KB of client side code • 100 x 250 KB = 25 MB • Not counting images, adm tree, support code etc. • Download times:28k8 modem: ~ 150 minsDual ISDN: ~ 34 mins
Deploying an application (III) • Compressing .r code saves just 20 % • Initial deployment on CD possible (with WebClient image) • However, even small application updates turn into megabytes to deploy • Did we choose internet to limit deployment possibilities?
Three Golden Rules: • Reduce AppServer calls • Reduce network traffic • Reduce client .r code
Consider this window again: What do we really really need?
Apply the Golden Rules • A single AppServer call should do • Do not send all viewer properties for all records • Create windows dynamically
A single AppServer call should do • All data for the entire window should be prepared on the server and returned in a single call • The server needs to know about the screen configuration • Describe screen definitions in a database (‘Repository’)
Do not send unnecessary data • The browser shows just 3 fields • Fetch the viewer data only when needed • Activate a time delay when the user scrolls • The ideal call returns just a single packet (up to about 1,500 bytes)
Do not send unnecessary data (2) Ask yourself: • Does the client REALLY need this information from the server? • Do I send information to the server that the server already knows?
Do not send unnecessary data (3) • The browser shows just 3 fields • Fetch the viewer data only when needed • Activate a time delay when the user scrolls • A call should ideally return just a single packet (1500 bytes)
3: Create windows dynamically • Send repository data to the client • Client creates all screens dynamically • NO r-code deployment at all! • In V9.1 this is NOT fiction
New startup statistics • Startup time 2 seconds on 28k8 • Client code always resides in memory • A single AppServer call • 41 KB of server-side r-code to start • 6 KB of data received • 100 rows cached on client
Why do we show the first 100? • The example window shows the first 100 customers • What are the chances that this is useful? • The first thing the user probably will do is find the customer she needs • Why start with a useless data transfer?
A new approach: start empty Let the user enter her selections first
A new approach: start empty Transfer only what the user asks for
Results for the new approach • 1 second response time on 28k8 for each user interaction • Just 2-3 KB received on each call • The application is now usable with a 28K8 modem and simply fast on anything better
How does it all work? • Write a generic ‘rendering’ program for the client (ui.p) • Introduce a repository • Write a generic program on the server that can merge repository information with data
The Big Picture AppServer WebClient uihooks.p blhooks.p Repository ui.p bl.p Application data Application data Appserver boundary
The implementation getscreen.p Repository ui.p save.p Application data Application data Delete.p Appserver boundary
ui.p Creates: • Dynamic windows and dialogs • Dynamic field level widgets • Dynamic panels • Dynamic browsers And fills them with dynamic data from the server
getscreen.p • Fetches screen description from repository • Creates dynamic database queries for each data set • Puts the results in dynamic temp-tables • Returns dynamic data and static screen description to client
Dynamic Temp-Tables • New in Progress v9.1 • Created specifically for dynamically passing data from an AppServer to a WebClient • Create temp-table structures on the fly • Requires only run-time Progress
Dynamic Temp-Tables (2) • Move data from getscreen.p to ui.p without knowing about db tables at compile time • Create in getscreen.p at runtime • Pass definitions & data to ui.p using new OUTPUT TABLE-HANDLE parameter
Creating a Dynamic Temp-Table DEFINE VARIABLE hTemp as HANDLE NO-UNDO CREATE TEMP-TABLE hTemp ASSIGN hTemp:UNDO = FALSE. • Just like any dynamic widget • Now we have created an empty structure • Use methods to define the structure
Dynamic Temp-Table Methods CREATE-LIKE ADD-FIELDS-FROM ADD-NEW-FIELD ADD-LIKE-FIELD ADD-LIKE-INDEX ADD-NEW-INDEX ADD-INDEX-FIELD
Use ADD-NEW-FIELD • Most versatile • We may want to overrule some dictionary properties in the repository • We want to add some extra fields such as the database ROWID of a record • Check the return value for errors
ADD-NEW-FIELD ADD-NEW-FIELD( field-name-exp, datatype-exp [ , extent-exp [ , format-exp [ , initial-exp [ , label-exp [ , column-label-exp ] ] ] ] ] )
ADD-NEW-FIELD examples hTemp:ADD-NEW-FIELD ("rowid","rowid”). hTemp:ADD-NEW-FIELD (hField:NAME, /* from dd */ hField:DATA-TYPE, /* from dd */ hField:EXTENT, /* from dd */ repository.cFormat, repository.cInitial, repository.cSideLabel repository.cColumnLabel).
Indexes • We do not need any indexes on these temp-tables • The client shows the records in the order in which they were created • Any server-side sorting included • Client may sort the records locally as well
Prepare the Temp-Table • hTemp:TEMP-TABLE-PREPARE(<name>). • ‘Compiles’ the temp-table at run time • Check the return value for errors • Provide the name argument for debugging purposes • Now you can fill the new structure with data… dynamically
Populate the Dynamic Temp-Table • We need a buffer handle for this: DEF VAR hDefault AS HANDLE NO-UNDO. hDefault = hTemp:DEFAULT-BUFFER-HANDLE. • But how do we get the data into the dynamic temp-table?
Populate the dynamic temp-table • First we need to create a dynamic buffer for each database table involved in the query: DEF VAR cTable AS CHAR INITIAL “customer”. DEF VAR hData AS HANDLE NO-UNDO. CREATE BUFFER hData for table cTable.
Populate the dynamic temp-table • Then we create a dynamic query: DEF VAR hQuery AS HANDLE NO-UNDO. CREATE QUERY hQuery. hQuery:ADD-BUFFER(hData). hQuery:QUERY-PREPARE(SUBSTITUTE( “FOR EACH &1 NO-LOCK”, cTable)).
Populate the dynamic temp-table • Then we open the query and buffer-copy the data: hQuery:QUERY-OPEN(). hQuery:GET-FIRST(). DO WHILE hQuery:QUERY-OFF-END = FALSE: hDefault:BUFFER-CREATE(). hDefault:BUFFER-COPY(hData). hQuery:GET-NEXT(). END.
Returning the dynamic temp-table • Don’t forget to fill the ROWID field as well • Maximize the number of records to be returned (e.g. 100 or even 50) • The new dynamic temp-table (meta schema + data) is returned to the client using OUTPUT PARAMETER TABLE-HANDLE • DELETE OBJECT hTemp. (never forget!) • Delete magically postponed by Progress