Survey
* Your assessment is very important for improving the workof artificial intelligence, which forms the content of this project
* Your assessment is very important for improving the workof artificial intelligence, which forms the content of this project
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