Transcript ADD-NEW

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
AppServer
DB
Need AppServer to talk to a database
 Separate User Interface from Business
Logic

Run Progress over inter/intranet
Internet
or
Intranet
AppServer
Full GUI capabilities on end user PC
 WebClient does NOT run in browser

DB
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
Consider the following window
Window architecture





Built in ADM II
SDO, SmartDataBrowser,
SmartDataViewer, SmartPanel
Run with AppServer
RowsToBatch=100
Nothing fancy (we’ll get to that later)
Startup time 6 s (!) over 28k8
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 mins
Dual 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?
Surely there is a better way?!?
Three Golden Rules:
1.
2.
3.
Reduce AppServer calls
Reduce network traffic
Reduce client .r code
Consider this window again:
What do we really really need?
Apply the Golden Rules
1.
2.
3.
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
9600 baud
Demonstration
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
Application
data
data
Appserver
boundary
The implementation
getscreen.p
Repository
ui.p
save.p
Delete.p
Appserver
boundary
Application
Application
data
data
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



OUTPUT TABLE-HANDLE
3 OUTPUT TABLE-HANDLE
1
Repository
getscreen.p
ui.p
Appserver
boundary
2
Application
Application
data
data
Receiving the dynamic temp-table

The dynamic temp-table is received by
the client using
OUTPUT TABLE-HANDLE


Client must analyze meta schema + data
How the heck do we go about this?
Receiving the dynamic temp-table

First analyze the meta schema:
DEF VAR hData AS HANDLE NO-UNDO.
DEF VAR iField AS INT NO-UNDO.
DEF VAR hField AS HANDLE NO-UNDO.
hData = hTemp:DEFAULT-BUFFER-HANDLE.
DO iField = 1 TO hData:NUM-FIELDS:
hField = hData:BUFFER-FIELD(iField).
/* Access field properties here */
END.
Analyzing the meta schema




By inspecting the buffer fields we can find
out meta information
Note that the client receives a virtual table
Meta information includes data-type,
format and labels
Other information such as row and
column needs to be sent separately
Analyzing the meta schema




The client can use the meta information
creatively to render the user interface
Could even be tailored individually
The server does not care about the user
interface
However, the server DOES care about
the data and the relationships within it
Analyzing the data

Looks familiar already:
DEF VAR hQuery AS HANDLE NO-UNDO.
CREATE QUERY hQuery.
hQuery:SET-BUFFERS(hData). /* Just 1 */


Now open the query and fetch the records
A viewer should return a single record, a
browser may return many
Record scoping




Dynamic BUFFERS have no scope –
they are created in the session
Use a WIDGET-POOL to scope to your
procedure
You are responsible for releasing the
record to write it to the database
Then you must delete the buffer object
Record scoping
DEF VAR hBuffer AS HANDLE NO-UNDO.
CREATE BUFFER hBuffer for table
‘customer’.
DO TRANSACTION:
hBuffer:BUFFER-CREATE().
/* fill here */
hBuffer:BUFFER-RELEASE().
END. /* Transaction */
Memory leaks


Show up after a long time
Can be extremely difficult
to track down
Bites you at
deployment!
Memory leaks


Dynamic objects are
scoped to the session
You are responsible for
deleting objects when you
are done with them:
DELETE BUFFER hBuffer.
hBuffer = ?.
Memory leaks



Use WIDGET-POOLS to
scope objects to your
procedure
TABLE-HANDLES are created in the
SESSION WIDGET-POOL (whether you
like it or not) on both sides
You need to delete those explicitly
TABLE-HANDLES
DEFINE OUTPUT PARAMETER
TABLE-HANDLE hTemp.
/* Creates the table */
/* Fill hTemp with data */
DELETE OBJECT hTemp.
/* Will be deferred by Progress
until procedure goes out of
scope */
Dynamic Temp-Table Viewer



Helps you troubleshoot
dynamic temp-table
memory management
problems
One ADM II issue shown
here
Download tool from
www.v9stuff.com
Advantages of dynamic programming







Eliminate CRC errors
Create extremely powerful code
No more .r code to deploy
Even hardly any .r code on the server
Faster applications
Easier multi-database programming
Run Progress applications across the
internet
Drawbacks of dynamic programming







New paradigm to learn
More abstract, work with handles
Difficult to debug
Repository replaces code
Different deployment issues
More runtime errors
Possible query performance problems
More information
Progress documentation:

–
–




V9 Product Update Bulletin
Progress on the Web
www.v9stuff.com
Progressions magazine (www.wss.com)
Walvis Database Viewer
Progress Email Group (www.peg.com)
Question time
Progress Solutions for Internet
www.netsetup.nl
[email protected]
WebClient & WebSpeed consultants