Progress JSDO и Kendo UI
Kendo UI – это JavaScript фреймворк, который используется для создания Web и Mobile приложений с применением HTML5 и JavaScript. Он предоставляет набор разнообразных виджетов, которые могут быть связаны с различными источниками данных.
Progress JavaScript Data Object (JSDO) предоставляет комплексную модель данных и API для управления этими данными при сохранении их целостности. JSDO каталог определяет логическую схему и связь с удалённым источником данных. В каталоге также есть API для вызова удалённой бизнес логики в дополнение к простым операциям CRUD(create, read, update, delete — «создание, чтение, обновление, удаление»). JSDO разработан для работы с любым Web / JavaScript фреймворком. И естественно Progress JSDO и Kendo UI отлично работают вместе. В этой статье описывается один из способов их совместного использования для доступа к данным и бизнес логике через OpenEdge AppServer.
Виджеты Kendo UI используют Kendo UI DataSource для доступа к локальным и удалённым данным. Kendo UI DataSource является абстракцией над локальными и удалёнными данными, что позволяет гораздо легче запрашивать данные, заполнять ими виджеты, а затем отслеживать изменения этих данных. Транспортное свойство в Kendo UI DataSource определяет, как выполнять операции CRUD в DataSource, определив соответствующие свойства: создание, чтение, обновление, и удаление.
OpenEdge сервер может быть доступен через Kendo UI DataSource с использованием той же архитектуры, которая используется для Mobile в OpenEdge 11.2 и в более поздних версиях – JSDO.
JSDO управляет комплексным взаимодействием с OpenEdge AppServer. Он подключается к OpenEdge AppServer и выполняет ABL-код, который получает доступ к данным и запускает бизнес-логику. Такая ABL -программа называется бизнес-сущностью. JSDO зависит только от JavaScript и может быть интегрирован со многими JavaScript фреймворками с поддержкой HTTP-взаимодействий. Чтобы получить доступ к OpenEdge AppServer, операции CRUD для Kendo UI DataSource могут быть настроены для вызова соответствующих CRUD-операций в JSDO. Этот документ содержит примеры и объясняет, как JSDO используется с компонентами пользовательского интерфейса Kendo.
Использование JSDO из простой HTML страницы
Следующий пример показывает, как получить доступ к серверу OpenEdge из HTML-страницы с помощью JSDO:
<!DOCTYPE html> <html> <head> <title>Simple JSDO Usage</title> <script src="http://oemobiledemo.progress.com/jsdo/progress.jsdo.3.1.js"> </script> </head> <body> <!-- results will be written here by JavaScript --> <script> (function () { var serviceURI = "http://oemobiledemo.progress.com/MobilityDemoService", catalogURI = serviceURI + "/static/mobile/MobilityDemoService.json"; // create a new session object var session = new progress.data.Session(); session.login(serviceURI, "", ""); session.addCatalog(catalogURI); // create a JSDO var jsdo = new progress.data.JSDO({ name: 'dsCustomer' }); jsdo.subscribe('AfterFill', onAfterFillCustomers, this); // calling fill reads from the remote OE server jsdo.fill(); // this function is called after data is returned from the server function onAfterFillCustomers(jsdo, success, request) { // for each customer record returned jsdo.eCustomer.foreach(function (customer) { // write out some of the customer data to the page document.write(customer.data.CustNum + ' ' + customer.data.Name + '<br>'); }); } }()); </script> </body> </html>
JavaScript файл progress.jsdo.3.1.js, включённый в верхней части приведённого примера прямо под тегом <title>, содержит код для объектов сессии и JSDO. Используется версия – 3.1. Однако, и любая другая версия может быть использована с Kendo UI в данном документе.
Поддержка для JSDO обеспечивается двумя основными объектами:
- Session – управляет аутентификацией и операциями сессии
- JSDO – обеспечивает доступ к серверу OpenEdge, используя информацию в каталоге JSDO
Объявляем JavaScript переменные serviceURI и catalogURI. Эти настройки используются JSDO для доступа к сервису OpenEdge. serviceURI это адрес веб-приложения в мобильном сервисе OpenEdge. catalogURI это адрес файла JSDO-каталога (файл JSON, содержащий определение ресурсов доступных в OpenEdge сервисе). Он определяет схему и операции для каждого из ресурсов.
Разработчикам нет необходимости указывать любые другие URI адреса для доступа к данным. Например, разработчик может просто вызвать функцию fill(), чтобы считать данные с сервера и не нужно указывать URI, который соответствует операции чтения. Эта информация получена на основе данных, содержащихся в каталоге.
JSDO подписывается на событие AfterFill. Операции к серверу, такие как fill() (операция чтения) и saveChanges () (операции создания, обновления и удаления), являются асинхронными и требуют callback-функцию для обработки ответа сервера. Событие AfterFill вызывается, когда сервис OpenEdge возвращает данные в JSDO. В этом событии метод foreach() вызывается для перебора присланных результатов и отображении их на web-странице. JSDO также содержит ряд других методов для обработки данных. Например, getData(), find(), findById (), sort(), и addRecords().
Работу этого кода можно посмотреть здесь.
В этом примере можно заменить serviceURI и catalogURI на свой собственный мобильный сервис. О том, как создать мобильный сервис в Progress Developer Studio см. онлайн документацию .
Использование JSDO из компонента UI Grid
В следующем примере показана работа JSDO с Kendo UI Grid. Простой пример с демонстрационной страницы Kendo UI был использован в качестве отправной точки. В дополнение к CRUD-операциям, прямо из коробки Kendo UI Grid предлагает такие функции как пагинация(разбитие на страницы), пропуск записей, разбитие на колонки, адаптации размера к разрешению экрана и лёгкая смена тем оформления.
Используя этот пример, мы получаем визуально привлекательную сетку с базовой функциональностью, которая выглядит вот так (щёлкни для увеличения):
Для подключения Kendo UI Grid к сервису OpenEdge используется Kendo UI Transports в JSDO.
<!DOCTYPE html> <html> <head> <title>JSDO / Kendo UI Grid Example</title> <link rel="stylesheet" href="http://cdn.kendostatic.com/2014.3.1316/styles/kendo.common.min.css" /> <link rel="stylesheet" href="http://cdn.kendostatic.com/2014.3.1316/styles/kendo.default.min.css" /> <script src="http://cdn.kendostatic.com/2014.3.1316/js/jquery.min.js"></script> <script src="http://cdn.kendostatic.com/2014.3.1316/js/kendo.all.min.js"></script> <script src="http://oemobiledemo.progress.com/jsdo/progress.jsdo.3.1.js"></script> <style> html { font-size: 12px; font-family: Arial, Helvetica, sans-serif; } </style> </head> <body> <div id="example"> <div id="grid"></div> </div> <script> $(function () { var serviceURI = 'http://oemobiledemo.progress.com/CustomerService', catalogURI = serviceURI + '/static/mobile/CustomerService.json'; // create a new session object var session = new progress.data.Session(); session.login(serviceURI, '', ''); session.addCatalog(catalogURI); // create a JSDO var jsdo = new progress.data.JSDO({ name: 'Customer' }); // select the "grid" div with jQuery and turn it into a Kendo UI Grid $('#grid').kendoGrid({ // all Kendo UI widgets use a DataSource to specify which data to display dataSource: { transport: { // when the grid tries to read data, it will call this function // this could alternatively be a URL read: jsdoTransportRead }, error: function (e) { console.log('Error: ', e); } }, height: 375, // setting up most of the grid functionality is as easy as toggling properties on and off groupable: true, sortable: true, reorderable: true, resizable: true, selectable: true, pageable: { refresh: true, pageSizes: true, pageSize: 10, buttonCount: 5 }, columns: [ { field: 'CustNum', title: 'Cust Num', type: 'int', width: 100 }, { field: 'Name' }, { field: 'State' }, { field: 'Country' } ] }); // this function is called after data is returned from the server function jsdoTransportRead(options) { jsdo.subscribe('AfterFill', function callback(jsdo, success, request) { jsdo.unsubscribe('AfterFill', callback, jsdo); if (success) { options.success(jsdo.getData()); } else { options.error(request.xhr, request.xhr.status, request.exception); } }, jsdo); jsdo.fill(); } }); </script> </body> </html>
Работу этого кода можно посмотреть здесь.
Так как Kendo UI это JavaScript фреймворк, то единственное что необходимо для его использования, это включить общий файл CSS, файл CSS темы(по умолчанию в данном примере), библиотеки JQuery и Kendo UI JavaScript.
Этот пример создаёт объекты SESSION и JSDO, а затем настраивает транспорт в свойствах DataSource Kendo UI.
Все виджеты Kendo UI используют DataSource, чтобы определить какими данными они могут манипулировать. Kendo UI DataSource может определить конечные точки чтения, обновления, создания и удаления, которые называются “Транспорты”. В этом примере транспортом чтения установлена функция jsdoTransportRead – это функция, которая будет вызвана когда Kendo UI DataSource должен прочитать данные. Свойство “Read” может быть строка, объект или даже функция. Функция может быть определена сразу, либо это может быть ссылка на функцию jsdoTransportRead, которая помещена в отдельный файл JavaScript.
В вызове subscribe() указываются событие AfterFill, callback-функция и контекст. Callback-функция определяется сразу, а не просто с помощью ссылки на функцию.
JSDO в ней отписывается от обратного вызова, так что только одна callback-функция регистрируется на AfterFill. Callback-функция объявлена инлайново(сразу идёт описание функции) в функции jsdoTransportRead так, что методы options.success и options.error тоже в нужный момент вызываются. Этот подход используется потому, что операция чтения асинхронная. Метод options.success вызывается, если чтение выполнено успешно. Если операция чтения не удаётся, вызывается метод options.error и соответствующие параметры передаются таким образом, что обработчик ошибок определённый для Kendo UI DataSource может обработать ошибку.
Код в jsdoTransportRead может быть изменён в зависимости от потребностей приложения. Например, если используется мультитабличный DataSet, то код может быть изменён так, чтобы указывать на конкретную таблицу в JSDO:
function jsdoTransportRead(options) { var jsdo = this.jsdo; jsdo.subscribe('AfterFill', function callback(jsdo, success, request) { jsdo.unsubscribe('AfterFill', callback, jsdo); if (success) { jsdo.useRelationships = false; options.success(jsdo.eOrder.getData()); jsdo.useRelationships = true; } else options.error(request.xhr, request.xhr.status, request.exception); }, jsdo); jsdo.fill(); }
В этом примере используется jsdo.eOrder.getData() для получения данных для eOrder в мультитабличном DataSet. Свойство useRelationships в JSDO может быть установлено в false, в результате GetData() будет возвращать все записи, а не только на основании связей.
Функция jsdoTransportRead (и другие которые поддерживают создание, обновление и удаление) также может быть настроена для использования любого API в JSDO, например, foreach(), sort(), addRecords() и другие.
В этом примере код в jsdoTransportRead() является универсальным. Это не относится к конкретному ресурсу и может быть перемещен в отдельный файл JavaScript, так что его можно использовать повторно другими программами.
В следующем разделе показано, как это будет выглядеть.
Упаковка транспортной функции класса Kendo UI
В то время как сам JavaScript не имеет концепции “класс” как традиционные языки программирования, классы могут быть созданы в JavaScript с помощью функций. Kendo UI обеспечивает удобный метод расширения, который позволяет создавать классы гораздо проще. С помощью kendo.Class.extend, новый класс может быть определен, вместе с конструктором.
// Create a base class var JSDOTransport = kendo.Class.extend({ // The `init` method will be called when a new instance is created init: function (param1, param2) { // todo } });
Теперь, когда класс был определён, можно переместить всю логику и транспортные функции в класс, что позволит использовать этот класс снова и снова с различными URI сервисов и именами ресурсов.
// Begin class declaration var JSDOTransport = kendo.Class.extend({ // The `init` method will be called when a new instance is created init: function (serviceURI, catalogURI, resourceName) { // Create and configure the session object this._createSession(serviceURI, catalogURI); // Create the JSDO this.jsdo = new progress.data.JSDO({ name: resourceName }); // Create proxies to internal methods to maintain the correct 'this' reference this.transport = { read: $.proxy(this._read, this), create: $.proxy(this._create, this), update: $.proxy(this._update, this), destroy: $.proxy(this._destroy, this) } }, // methods with an "_" are private and are only to be used by the class _createSession: function (serviceURI, catalogURI) { this.session = new progress.data.Session(); this.session.login(serviceURI, '', ''); this.session.addCatalog(catalogURI); }, // the transports needed by the DataSource _read: function (options) { var jsdo = this.jsdo; jsdo.subscribe('AfterFill', function callback(jsdo, success, request) { jsdo.unsubscribe('AfterFill', callback, jsdo); if (success) options.success(jsdo.getData()); else options.error(request.xhr, request.xhr.status, request.exception); }, jsdo); jsdo.fill(); }, _create: function (options) { var jsdo = this.jsdo; var jsrecord = jsdo.add(options.data); jsdo.subscribe('AfterSaveChanges', function callback(jsdo, success, request) { jsdo.unsubscribe('AfterSaveChanges', callback, jsdo); var data; if (success) { if (request.batch && request.batch.operations instanceof Array && request.batch.operations.length == 1) { data = request.batch.operations[0].jsrecord.data; } options.success(data); } else options.error(request.xhr, request.xhr.status, request.exception); }, jsdo); jsdo.saveChanges(); }, _update: function (options) { var jsdo = this.jsdo; var jsrecord = jsdo.findById(options.data._id); try { jsdo.assign(options.data); } catch (e) { options.error(null, null, e); } jsdo.subscribe('AfterSaveChanges', function callback(jsdo, success, request) { jsdo.unsubscribe('AfterSaveChanges', callback, jsdo); var data; if (success) { if (request.batch && request.batch.operations instanceof Array && request.batch.operations.length == 1) { data = request.batch.operations[0].jsrecord.data; } options.success(data); } else options.error(request.xhr, request.xhr.status, request.exception); }, jsdo); jsdo.saveChanges(); }, _destroy: function (options) { var jsdo = this.jsdo; var jsrecord = jsdo.findById(options.data._id); try { jsdo.remove(); } catch (e) { options.error(null, null, e); } jsdo.subscribe('AfterSaveChanges', function callback(jsdo, success, request) { jsdo.unsubscribe('AfterSaveChanges', callback, jsdo); if (success) options.success([]); else options.error(request.xhr, request.xhr.status, request.exception); }, jsdo); jsdo.saveChanges(); } }); // End class declaration
Метод класса init() вызывается, когда создается новый экземпляр класса. Конструктор требует, чтобы serviceURI, catalogURI и resourceName были переданы в конструктор класса. Затем класс заботится о создания нового объекта SESSION и JSDO. Также в нем описаны все транспортные функции, так что они будут доступны после того как создан объект класса.
В JavaScript значение переменной ‘this’ постоянно меняется в зависимости от места его использования в коде. Это известно как “лексический контекст”. Использование JQuery метода $.proxy для вызова внутренних функций гарантирует, что значение “this” на самом деле относиться к данному классу, а не что-то еще, когда оно, в конечном счете используется в private методах _read, _create, _update и _destroy.
В _read, _create, _update и _delete функциях используются API-интерфейсы JSDO для выполнения соответствующей операции и в конечном итоге вызывают jsdo.saveChanges() для синхронизации изменений с OpenEdge службой. Код в функции обратного вызова AfterSaveChanges возвращает данные в Kendo UI DataSource, выполнив options.success(), или обрабатывает ошибки методом options.error().
В методе _create функция jsdo.add () добавляет запись в память JSDO. Вызов SaveChanges () посылает эти изменения на OpenEdge сервер, а затем обрабатывает ответ от сервера, убедившись, что DataSource и сервер содержат одинаковые данные.
В методе _update запись, которая должна быть обновлена, извлекается путем вызова jsdo.findById(). Извлеченная запись обновляется и вызывается метод SaveChanges() посылающая это обновление на OpenEdge сервер. Обратный вызов от сервера обрабатывается, чтобы убедиться, что источник данных и сервер теперь оба содержат одинаковые данные.
В методе _destroy функция jsdo.findById() вновь используется для поиска записи, которая должна быть удалена. Затем ее удаляют из памяти с помощью вызова jsdo.remove(). Вызов SaveChanges() еще раз сохраняет изменения на OpenEdge сервер. Наконец, пустой массив передается обратно в DataSource Kendo UI, так как операция удаления не возвращает никаких записей.
Ключевое слово “destroy” используется вместо “delete”, потому что “delete” является ключевым словом в JavaScript.
Теперь можно создать экземпляр класса, обеспечивая надлежащие параметры. В следующем примере класс JSDOTranport был перемещен в другой файл и подключен в страницу HTML.
<!DOCTYPE html> <html> <head> <title>Progress / Kendo UI Demo</title> <link rel="stylesheet" href="http://cdn.kendostatic.com/2014.3.1316/styles/kendo.common.min.css" /> <link rel="stylesheet" href="http://cdn.kendostatic.com/2014.3.1316/styles/kendo.default.min.css" /> <script src="http://cdn.kendostatic.com/2014.3.1316/js/jquery.min.js"></script> <script src="http://cdn.kendostatic.com/2014.3.1316/js/kendo.all.min.js"></script> <script src="http://oemobiledemo.progress.com/jsdo/progress.jsdo.3.1.js"></script> <script src="jsdoTransport.js"></script> </head> <body> <div id="example"> <div id="grid"></div> </div> <script> $(function () { var serviceURI = 'http://oemobiledemo.progress.com/CustomerService', catalogURI = serviceURI + '/static/mobile/CustomerService.json', resourceName = 'Customer'; // create a new instance of the JSDO transport class for 'Customer' // the class was included as part of the jsdoTransport file var customer = new JSDOTransport(serviceURI, catalogURI, resourceName); $("#grid").kendoGrid({ dataSource: { // define transports as the class functions transport: customer.transport, schema: { model: { id: '_id' } }, error: function (e) { console.log('Error: ', e); } }, height: 400, groupable: true, reorderable: true, resizable: true, sortable: true, pageable: { refresh: true, pageSizes: true, pageSize: 10, buttonCount: 5 }, editable: 'inline', toolbar: ['create'], columns: [ { field: 'CustNum', title: 'Cust Num', type: 'int', width: 100 }, { field: 'Name' }, { field: 'State' }, { field: 'Country' }, { command: ['edit', 'destroy'], title: ' ', width: '250px' } ] }); }); </script> </body> </html>
Код стал гораздо чище. Все, что нужно для создания надлежащего Kendo UI DataSource транспорта, который используют JSDO, это создать новый экземпляр класса JSDOTransport и передать в параметрах serviceURI, catalogURI и resourceName.
Использование Kendo UI DataSource между виджетами
В то время как Kendo UI DataSource может быть определен в виджете как объект конфигурации (как мы видели ранее), он также может быть определен как автономный объект(stand-alone). Это позволяет использовать его большим количеством виджетов. Помимо богатой коллекции виджетов управления данными, Kendo UI также включает в себя обширную библиотеку визуализации данных.
Эти виджеты используют новые стандарты HTML5 SVG для отображения анимированных и интерактивных графиков прямо в браузере. Они также заботятся о работе в старых браузерах (IE 7), где SVG не поддерживается.
Для того чтобы добавить диаграмму на этой странице, которая отображает те же самые данные в Grid, то в первую очередь необходимо вытащить объявление DataSource в отдельный объект.
var serviceURI = base + 'http://oemobiledemo.progress.com/CustomerService', catalogURI = base + '/static/mobile/CustomerService.json', resourceName = 'Customer'; // create a new instance of the JSDO transport class for 'Customer' var customer = new window.app.JSDOTransport(serviceURI, catalogURI, resourceName); // create a datasource that can be shared between widgets var customerDS = new kendo.data.DataSource({ transport: customer.transport, schema: { model: { id: '_id' } }, error: function (e) { console.log('Error: ', e); } }); // the grid’s dataSource can now be set directly to the customerDS object $("#grid").kendoGrid({ dataSource: customerDS, height: 350, groupable: true, reorderable: true, resizable: true, sortable: true, pageable: { refresh: true, pageSizes: true, pageSize: 10, buttonCount: 5 }, editable: "inline", toolbar: ["create"], columns: [ { field: "CustNum", title: "Cust Num", type: "int", width: 100 }, { field: "Name" }, { field: "State" }, { field: "Country" }, { command: ["edit", "destroy"], title: " ", width: "250px" } ] });
Теперь можно добавить любой другой виджет Kendo UI или компонент визуализации данных и использовать тот же источник данных.
Использование JSDO с Kendo UI Charts
Kendo UI имеет обширную библиотеку для построения графиков и визуализации данных. Данные OpenEdge полученные с помощью JSDO могут отображается с помощью Kendo UI Charts. Данные извлекаемые JSDO обрабатывается в JavaScript затем передается в Kendo UI Charts с использованием формата, что он ожидает. Kendo UI DataSourse не используется в этих примерах.
Следующие примеры демонстрируют использование JSDO с Kendo UI Charts.
Круговая диаграмма: график продаж на человека за 2014 год, использует JSDO для вызова операции под названием MonthlySales(), которая возвращает ежемесячные продажи за SalesRep, данные обрабатываются для расчета общих продаж и предоставления данных, которые использует Kendo UI Pie Charts .
Работу кода можно посмотреть здесь.
Гистограмма(Bar Chart): График ежемесячных продаж за 2014 год, сочетает в себе Kendo UI Grid и Kendo UI Bar Chart. Он использует ту же INVOKE операцию что и в предыдущем примере, но обрабатывает данные для расчета общего объема продаж за месяц для SalesRep выбранного в Grid. Если не выбран ни один SalesRep, показываются общие данные для всех SalesRep.
Работу кода можно посмотреть здесь.
На этом у меня всё.
Есть вопрос? Спросите...
Для отправки комментария вам необходимо авторизоваться.
1 Комментарий
WebSpeed Generic Service for the JSDO using OpenEdge 11.6 – See more at: https://community.progress.com/community_groups/mobile/m/documents/2677#sthash.YwFccSk7.dpuf