A Simple Approach to Modernize WebSpeed with Kendo UIx
Download
Report
Transcript A Simple Approach to Modernize WebSpeed with Kendo UIx
A Simple Approach to
Modernize WebSpeed
with Kendo UI
Jordi Sastre, June 2016
About the Presenter
Discovered Progress in 1991
Joined Progress Technical Support in 1995 in The Netherlands
Moved to IT and Bedford in 2001
Currently IT Software Architect in Progress IT
Always trying to find the simplest way to provide the most value
2
Objective
Describe an approach to combine WebSpeed and Kendo UI where both technologies are
used to their best abilities: OpenEdge ABL in the backend for business logic and Kendo UI in
the frontend for rich web presentation
Based on real experience from business applications developed and used internally at
Progress
The main idea is to communicate concepts, not code
Provide some hints (yes, with code)
3
Agenda
JavaScript Frameworks & Kendo UI
Development Approach
Architecture
Demo of simple Kendo UI application
Hints and code snippets
Summary
4
JavaScript Frameworks
Most common technology for developing Web rich applications (RIA)
Wikipedia lists 54 RIA frameworks, 29 of them based on JavaScript
How to choose a JavaScript framework?
• 1) Do your research, try them, and pick the one you like the most
• 2) Trust Progress and use Kendo UI
You want to spend your effort on solving business needs, not technology challenges
5
Kendo UI
It’s not easy – Ramp up may take a while depending on your background
Requires HTML, JavaScript and jQuery knowledge, the more the better
Normally used along with other frameworks such as AngularJS and Bootstrap
There isn’t a single way to implement a functionality, often times there isn’t “the correct way”
The outcome is good
Kendo online help:
• Google
• http://docs.telerik.com
• http://www.telerik.com/support/demos
• http://www.telerik.com/forums
6
Development Approach
Approach Design Development
• Choose an approach for all developments
– Think first, code later
– There are around 1,000,001 different ways to do something (i.e. “there is my way and around a million
more”)
– Just choose your way and stick to it
– The approach should result in a list of principles
• Make sure all applications and components are designed following the principles
• If the above is successful, development is easy
7
Architecture Evolution
Progress/OpenEdge architectures have evolved
4GL Programs
Data.db
• Character User Interface (CHUI)
• Include files
• Database triggers
8
Architecture Evolution
Common
Procedures
Data.db
GUI Programs
• Graphical User Interfaces (GUI)
• Event driven
• Client/Server
• Share code and data
• SmartObjects
9
Architecture Evolution
WebSpeed
AppServer
Web Programs
AppServer
Procedures
Data.db
GUI Programs
Batch
Processes
Integration
• Web Browsers – HTML and form submission
• WebSpeed broker
• Separation of Logic – AppServer
• OpenEdge Reference Architecture
• Integration becoming more relevant
10
Architecture Proposal
Web Browser
WebSpeed
HTML
JavaScript
API Services
Ajax Services
Integration
• Rich Internet Applications (RIA)
• JavaScript
• Asynchronous Services – AJAX
• Integration is critical
11
Data.db
Batch
Processes
Architecture Proposal
Web Browser
AppServer
WebSpeed
HTML
JavaScript
API Services
Ajax Services
Integration
• Rich Internet Applications (RIA)
• JavaScript
• Asynchronous Services – AJAX
• Integration is critical
12
Data.db
Batch
Processes
Architecture Proposal
Web Browser
PAS for OE
HTML
JavaScript
API Services
Ajax Services
Integration
• Rich Internet Applications (RIA)
• JavaScript
• Asynchronous Services – AJAX
• Integration is critical
13
Data.db
Batch
Processes
Architecture Proposal
Web Browser
HTML
JavaScript
WebSpeed
API Services
Ajax Services
Integration
14
Data.db
Batch
Processes
Architecture
Web Browser
HTML
JavaScript
WebSpeed
API Services
Ajax Services
Integration
•
Database
15
Data.db
Batch
Processes
Architecture
Web Browser
HTML
JavaScript
WebSpeed
API Services
Ajax Services
Integration
•
Data.db
Batch
Processes
Set of APIs to perform all database activity, from simple CRUD operations to complex business
functions
–
–
–
Prepared to be called through an AppServer or local procedures
Contain strong validation of input parameters (business rules)
This is were the application business logic lives
16
Architecture
Web Browser
WebSpeed
HTML
JavaScript
API Services
Ajax Services
Integration
•
Data.db
Batch
Processes
Ajax components to expose some of the APIs as REST-like Web Services
–
–
Their main function is to convert OpenEdge input/output parameters to/from JSON
Contain little or no business logic
17
Architecture
Web Browser
HTML
JavaScript
WebSpeed
API Services
Ajax Services
Integration
•
UI components made of HTML+Kendo UI
–
–
Contain look & feel, user interaction and front end validation
Should not contain application business logic
18
Data.db
Batch
Processes
Architecture
Web Browser
WebSpeed
HTML
JavaScript
API Services
Ajax Services
Integration
•
Data.db
Batch
Processes
Batch Processes and Integration
–
–
–
Run from plan Progress clients or called by APIs
Can use the APIs from AppServer or called locally
Batch process can use direct database connection for queries for better performance
19
Architecture
Web Browser
HTML
JavaScript
WebSpeed
API Services
Ajax Services
Integration
20
Data.db
Batch
Processes
How do Components Pass Data?
Query-string
JSON
JavaScript
GET-FIELD()
READ-JSON()
INPUT PARAM
TEMP-TABLE
DATASET
Ajax Services
JSON
WRITE-JSON()
API Services
OUTPUT PARAM
TEMP-TABLE
DATASET
RETURN-VALUE
Kendo UI uses jQuery’s $.ajax, which uses JSON
Query-string parameters for requests are good if there are few and can be public
Parameters can also be sent in JSON format as POST
Data should be sent in JSON as POST
Ajax services receive query string parameters and/or JSON and return JSON
Ajax services communicate with APIs using standard 4GL structures
Ajax services can also return HTTP codes to Kendo UI (“200 OK”, “404 Not found”,
“401 Unauthorized”,…) – You can choose the text
21
Data.db
OpenEdge Developer Kits (OEDK) Subscription Management
Developer
ESD
Kendo+Java
Subscription Administrator
CSS
Kendo+Java
OE AppServer
Apache + WebSpeed
Progress COM User
IT User
Ajax Services
4GL
Application UI
HTML+Kendo
Unit Test UI
4GL
OE Client
Integration
4GL
APIs
4GL
OE Database
Data.db
Processes
4GL
SubEngine
22
Event Console
Event
Receivers
OE Database
Events.db
Event
Processors
23
Event Console
Event
Receivers
Apache + WebSpeed
User
Console UI
HTML+Kendo
OE Database
Ajax Services
4GL
APIs
4GL
Events.db
Event
Processors
24
Demo
25
Event Console
Web Browser
Ajax Services
API Services
UpdateStatus.w
UpdateEvent.p
Events.db
Main.html
Query.w
QueryEvents.p
Main.w
EventConsole.ini
ttEvent.i
wsbp.i
libPSCI2.p
26
libSuper.i
Database
Table: Event
10
20
30
40
50
60
70
80
90
100
PUA.
..A.
..A.
..A.
..AW
..AW
..A.
..A.
EventID
AppSource
AppTarget
EventText
SourceKey
TargetRef
EventStatus
Created
Updated
Processed
I64
C
C
C
C
C
C
DT
DT
DT
>>>,>>>,>>>,>>9
X(20)
w:40
X(20)
w:40
X(65)
w:130
X(30)
w:60
X(20)
w:40
X(5)
w:10
99/99/99 HH:MM:SS
99/99/99 HH:MM:SS
99/99/99 HH:MM:SS
Generated from DB sequence EventID
Application or QDoc that has generated the event
Target for the event (system + "." + data entity)
Event text
CHR(1)-delimited list of key fields, or blank
File name, return value or blank depending on tar
NEW,OK,ERROR
When the event has been first created
When a duplicate event has been last received
When the event has been last processed (OK or ERR
idx1
idx2
idx3
idx4
idx5
idx6
idx7
idx8
+EventID
+AppSource +AppTarget +SourceKey
+AppTarget
+EventStatus
+EventText
+SourceKey
+AppSource +Created
+AppTarget +Processed
27
API Services – QueryEvents.p
Receives a query (WHERE clause) as parameter
Receives paging parameters
Executes dynamic query
Returns TEMP-TABLE with records that match the query
Decisions
• Parameters versus query
• Client paging versus server paging
28
API Services – QueryEvents.p – Paging
DEF INPUT
PARAM ipcQuery
AS CHAR NO-UNDO.
DEF INPUT
PARAM ipiPageSize
AS INT
NO-UNDO.
DEF INPUT
PARAM ipiPageNum
AS INT
NO-UNDO.
DEF OUTPUT PARAM opiTotRecords AS INT
NO-UNDO.
29
API Services – QueryEvents.p – Dynamic Query
iSkip = ipiPageSize * (ipiPageNum - 1).
hQuery:QUERY-PREPARE("FOR EACH Event NO-LOCK WHERE " + ipcQuery) NO-ERROR.
IF ERROR-STATUS:NUM-MESSAGES = 0 THEN DO:
WriteLog("INFO","Query: " + hQuery:PREPARE-STRING).
WriteLog("INFO","Using indexes: " + hQuery:INDEX-INFORMATION(1)).
hQuery:QUERY-OPEN.
IF iSkip > 0 THEN
hQuery:REPOSITION-TO-ROW(iSkip + 1) NO-ERROR.
iCount = 0.
DO WHILE NOT hQuery:QUERY-OFF-END
AND (IF ipiPageSize = 0 THEN TRUE ELSE iCount < ipiPageSize):
hQuery:GET-NEXT().
IF AVAIL Event THEN DO:
CREATE ttEvent.
BUFFER-COPY Event TO ttEvent.
iCount = iCount + 1.
END.
END.
END.
opiTotRecords = hQuery:NUM-RESULTS.
30
API Services – QueryEvents.p – Paging
iSkip = ipiPageSize * (ipiPageNum - 1).
hQuery:QUERY-PREPARE("FOR EACH Event NO-LOCK WHERE " + ipcQuery) NO-ERROR.
IF ERROR-STATUS:NUM-MESSAGES = 0 THEN DO:
WriteLog("INFO","Query: " + hQuery:PREPARE-STRING).
WriteLog("INFO","Using indexes: " + hQuery:INDEX-INFORMATION(1)).
hQuery:QUERY-OPEN.
IF iSkip > 0 THEN
hQuery:REPOSITION-TO-ROW(iSkip + 1) NO-ERROR.
iCount = 0.
DO WHILE NOT hQuery:QUERY-OFF-END
AND (IF ipiPageSize = 0 THEN TRUE ELSE iCount < ipiPageSize):
hQuery:GET-NEXT().
IF AVAIL Event THEN DO:
CREATE ttEvent.
BUFFER-COPY Event TO ttEvent.
iCount = iCount + 1.
END.
END.
END.
opiTotRecords = hQuery:NUM-RESULTS.
31
API Services – QueryEvents.p – Paging
iSkip = ipiPageSize * (ipiPageNum - 1).
hQuery:QUERY-PREPARE("FOR EACH Event NO-LOCK WHERE " + ipcQuery) NO-ERROR.
IF ERROR-STATUS:NUM-MESSAGES = 0 THEN DO:
WriteLog("INFO","Query: " + hQuery:PREPARE-STRING).
WriteLog("INFO","Using indexes: " + hQuery:INDEX-INFORMATION(1)).
hQuery:QUERY-OPEN.
IF iSkip > 0 THEN
hQuery:REPOSITION-TO-ROW(iSkip + 1) NO-ERROR.
iCount = 0.
DO WHILE NOT hQuery:QUERY-OFF-END
AND (IF ipiPageSize = 0 THEN TRUE ELSE iCount < ipiPageSize):
hQuery:GET-NEXT().
IF AVAIL Event THEN DO:
CREATE ttEvent.
BUFFER-COPY Event TO ttEvent.
iCount = iCount + 1.
END.
NUM-RESULTS() returns the total number
END.
of records that match the query if the
END.
query can be bracketed with two or more
opiTotRecords = hQuery:NUM-RESULTS.
indexes (WHERE and BY)
32
API Services – UpdateEvent.p
Receives an event ID and a new status as parameters
Updates the database
DEF INPUT PARAM ipiEventID
AS INT64 NO-UNDO.
DEF INPUT PARAM ipcNewStatus AS CHAR NO-UNDO.
FIND Event EXCLUSIVE-LOCK WHERE Event.EventID = ipiEventID NO-ERROR.
IF AVAIL Event THEN DO:
Event.EventStatus = ipcNewStatus.
Event.Processed
= NOW.
RETURN "OK".
END.
ELSE
RETURN "EventUpdate.p did not find event " + STRING(ipiEventID).
33
Ajax Services – Query.w
Receives filters, paging and sorting parameters in query-string
Builds query for the WHERE clause
Calls API QueryEvents.p passing query, paging and sorting parameters
Converts returned TEMP-TABLE to JSON
Returns the result or error message
34
Ajax Services – Query.w – Query-string parameters
http://..../Query.w?event_id=&app_source=&app_target=&so
urce_key=&status=NEW&start_date=&end_date=&take=10&skip=
0&page=1&pageSize=10&sort[0][field]=EventID&sort[0][dir]
=asc&sor[1][field]=Created&sort[1][dir]=desc
serverPaging: true,
serverSorting: true,
FOR EACH Event NO-LOCK
WHERE EventStatus = 'NEW'
BY EventID BY Created DESCENDING
35
Ajax Services – Query.w – Building query
cAppSource
= GET-FIELD("app_source").
(…)
cQuery = "TRUE".
IF cAppSource > "" THEN
cQuery = cQuery + " AND AppSource = '" + cAppSource + "'".
IF cAppTarget > "" THEN
cQuery = cQuery + " AND AppTarget = '" + cAppTarget + "'".
IF cSourceKey > "" THEN
cQuery = cQuery + " AND SourceKey CONTAINS '" + cSourceKey + "'".
IF cStatus > "" THEN
cQuery = cQuery + " AND EventStatus = '" + cStatus + "'".
IF cStartDate > "" THEN
cQuery = cQuery
+ " AND Created >= '" + STRING(UnformatDate(cStartDate)) + "'".
IF cEndDate > "" THEN
cQuery = cQuery
+ " AND Created < '" + STRING(UnformatDate(cEndDate)) + "'".
36
Ajax Services – Query.w – Parsing sort
FUNCTION ParseSort RETURNS CHAR (INPUT ipc AS CHAR):
DEF VAR cSort AS CHAR NO-UNDO INIT "".
DEF VAR i
AS INT NO-UNDO.
DO i = 1 TO NUM-ENTRIES(ipc):
IF ENTRY(i,ipc) MATCHES "sort[*][field]" THEN DO:
cSort = cSort + " BY " + GET-VALUE(ENTRY(i,ipc)).
IF GET-VALUE(REPLACE(ENTRY(i,ipc),"[field]","[dir]")) = "desc" THEN
cSort = cSort + " DESCENDING".
END.
END.
RETURN cSort.
END FUNCTION.
cSort = ParseSort(GET-VALUE(?)).
37
Ajax Services – Query.w – Returning Data
hTable = TEMP-TABLE ttEvent:HANDLE.
hTable:WRITE-JSON("LONGCHAR",lcResponse).
lcResponse = '~{"TotalRecords":' + STRING(iTotRecords) + ','
+ SUBSTR(lcResponse,2).
output-content-type ("text/json; charset=" + GetMimeCP(SESSION:CPSTREAM)).
{&OUT-LONG} lcResponse.
{&OUT} SKIP.
QUIT.
38
Ajax Services – UpdateStatus.w
Receives comma-delimited list of event IDs to update
Receives new status
Calls API UpdateEvent.p for each event
39
Ajax Services – HTTP errors
RUN QuitProgram("Error ...").
RUN QuitProgram("").
PROCEDURE QuitProgram:
DEF INPUT PARAM ipcMessage AS CHAR NO-UNDO.
IF ipcMessage <> "" THEN DO:
ipcMessage = TRIM(ipcMessage + CHR(10) + GetErrorStatus()).
WriteLog("ERROR",ipcMessage).
output-http-header("Status", "500 " + ipcMessage).
END.
WriteLog("INFO",THIS-PROCEDURE:NAME + " completed in "
+ FormatElapsed(ETIME - iTime)).
QUIT.
END PROCEDURE.
40
HTML & Kendo UI – Main.html
Look and feel defined by HTML and Kendo CSS
HTML contains placeholders for Kendo widgets
Ajax services defined in Kendo DataSources
Kendo Grid bound to Kendo DataSource
41
Kendo Datasource
var dsEvents = new kendo.data.DataSource({
type: "json",
transport: {
read: {
url: "[[WSBP-APPURL]]/Events/EventConsole/ax/Query.w",
data: function() {
return {
event_id: $("#event_id").val(),
app_source: $("#app_source").val(),
app_target: $("#app_target").val(),
source_key: $("#source_key").val(),
status: $("#status").val(),
start_date: $("#start_date").val(),
end_date: $("#end_date").val()
};
}
}
},
schema: {
total: "TotalRecords",
data: "ttEvent"
},
serverPaging: true,
pageSize: 25,
serverSorting: true,
sort: {field: "EventID", dir: "asc"},
requestStart: function() { popupNotif.info("Contacting server..."); },
requestEnd: function(e) { popupNotif.hide(); },
error: function(e) { KBPError(e); }
});
42
WebSpeed Ajax Service
Query-string parameters
Returned parameter
Output JSON
Paging
Sorting
Custom logic
Kendo Grid
var grid = $("#grid").kendoGrid({
dataSource: dsEvents,
height: 450,
noRecords: true,
resizable: true,
sortable: { mode: "multiple" },
selectable: "row",
pageable: {
pageSizes: ["10","25","50","100","500","1000","All"],
buttonCount: 5
},
columns: [
{ field: ""
,title: "" },
{ field: "EventID"
,title: "Event ID", width: 90 },
{ field: "AppSource"
,title: "Source" },
{ field: "AppTarget"
,title: "Target" },
{ field: "SourceKey"
,title: "Source key" },
{ field: "EventStatus" ,title: "Status", width: 90 },
{ field: "Created"
,title: "Created", type: "date", format: "{0:dd-MMM-yy HH:mm:ss}"
}
]
}).data("kendoGrid");
DataSource
Sorting
Paging
Columns
TT field names
43
Error Trapping
function KBPError(e) {
HTTP Error
popupNotif.error("ERROR");
if(e.status != undefined) {
if(e.xhr.statusText != "OK")
System Errors
/* Returns managed HTTP return code */
popupNotif.error(e.xhr.statusText);
}
else if(e.xhr.responseText != undefined)
/* Returns runtime errors (see WebSpeed log for details) */
popupNotif.error(e.xhr.responseText);
}
44
Summary
JavaScript frameworks is a recommended option for application modernization
Kendo UI is a good JavaScript framework
• It plays nice with the top two players: jQuery and Angular
Take your time to design the architecture
Separate the code
• Front-end / Back-end
• User Interface / Application logic
45