ENTERPRISE

Delphi / C++BuilderアプリケーションをWebアプリケー...

Written by Hitoshi Fujii on . Posted in ENTERPRISE

既存のC/Sアプリケーションは引き続き利用したいものの、Webブラウザによるアクセスや、一部機能を社内の別システムや社外システムと連携させたいといった要求が上がることがあると思います。このようなときに、Delphi / C++Builderでは、既存アプリケーション資産を有効に活用しながら拡張する手法を選択できます。

また、新たにエンバカデロの開発ツールラインナップに加わったSenchaを用いると、効率的にモダンなWebアプリケーションを構築できるわけですが、そのバックエンドとして既存のDelphi / C++Builderアプリケーションを拡張するという手法を選択できます。

Senchaとは

まず、Webアプリケーション開発プラットフォームとしてのSenchaについて簡単に紹介しておきましょう。Senchaは、JavaScriptベースのWebアプリケーション構築フレームワークで、Sencha Ext JSと呼ばれるフレークワークがその中核です。Ext JSには、豊富なコンポーネントが用意されており、グリッドやチャートといったC/Sアプリケーションでもなじみのある高度なユーザーインターフェイスを簡単に作成できるようになっています。

ユーザーインターフェイスの構築はコードベースを行うことも可能ですが、Sencha Architectと呼ばれるビジュアルツールを用いると、DelphiやC++Builderと同じように、コンポーネントのドラッグ&ドロップ、プロパティ/イベントの設定という効率的な手法で、Webアプリケーションを構築できます。

Senchaは基本的にクライアントサイド(ブラウザ)で動作します。高度なユーザーインターフェイスは、JavaScriptとHTML5によって実現されており、主要なブラウザ、デスクトップPC、タブレット、スマートフォンなど、さまざまなデバイスで最適な動作をします。Senchaが扱うデータは、「Model」「Proxy」「Store」と呼ばれるオブジェクトによって管理します。これらオブジェクトにより、Senchaアプリケーションは、Web APIを通じてJSON形式のデータを取得し、UIに表示、編集、更新することができます。

Senchaはデータベースや企業システムからのデータ取得、更新を、こうしたオープンな仕組みによってサポートしており、特定のバックエンド実装に依存しないようになっています。

Delphi / C++Builderアプリケーションとの接続

Delphi / C++Builderも、Senchaと接続するためのWeb APIを構築できます。そこで使用するデータ形式もJSONであり、最新バージョンではSenchaでより扱いやすいJSON形式の作成もサポートしています。

既存のDelphi / C++Builderアプリケーションから見れば、Senchaアプリケーションもクライアントのひとつであり、同じようにC/Sアプリケーションの構成に組み入れることができます。ただし、Web APIはデータベースとクライアントの中間に位置する「中間サーバー」として構築する必要があり、この部分に、Delphi / C++Builderのサーバーサイドアプリケーション構築技術を用いることができます。

Delphi / C++Builderでは、REST/JSONによるWeb APIを構築/運用するための技術としてRAD Serverが提供されています。RAD Serverを用いれば、データモジュールへのドラッグ&ドロップ操作でロジックコンポーネントを配置し、プロパティ/イベントを設定する効率的なビジュアル開発の手法で、カスタムWeb APIを作成することができます。RAD Serverには、コネクションプールを用いた効率的なFireDACの使用、ユーザー認証やモバイルアクセスのサポートなど、実用的なサーバーサイドアプリケーションを構築するために必要とされる機能があらかじめ用意されています。

RAD Serverによって作成されたWeb APIによって既存システムへのアクセス性を提供すれば、SenchaによるWebアプリケーションが既存のC/SシステムのWeb拡張として機能するようになります。

具体的な手法を知るには...

Delphi / C++BuilderアプリケーションをWeb拡張するその具体的な手法を学ぶには、2018年3月15日、東京・秋葉原のUDX GALLERY NEXTで開催される「第35回 エンバカデロ・デベロッパーキャンプ」へお越しください。このイベントでは、Delphi / C++BuilderとSenchaを組み合わせた実践的な開発手法をご紹介する予定です。

「第35回 エンバカデロ・デベロッパーキャンプ」の詳細はこちら >

関連情報


Delphi Labs: DataSnap code samples updated to XE3

Written by Tim DelChiaro on . Posted in ENTERPRISE

This is a repost of a blog post from Pawel Glowacki. Click the blog post title below to visit Pawel's blog to see the original post, comments and additional blog posts.

  Delphi Labs: DataSnap code samples updated to XE3

It seems that it is my very first post in the New Year 2013, so I wish You all the best and great time writing all kinds of programs. I’m sure it is going to be a great year for Delphi and Marco is predicting it as well, especially that next month Delphi is going to become adult!

Two years ago I have put together a serie of Delphi DataSnap tutorials called "Delphi Labs" and many people found it useful, so I thought that it is a high time to at least update code samples for every tutorial so they compile with the latest version of Delphi which is right now XE3.

Below is the list of updated Delphi Labs DataSnap episodes, with information about changes made to the original XE code samples and the actual link to CodeCentral. In order to keep things simple I have reuploaded every demo source code to the same CodeCentral URL, so links to code remain the same. Inside every zip file there are two top level folders: "XE" for original files and "XE3" for updated source code.

You can find the master page with all DelphiLabs DataSnap XE tutorials at www.embarcadero.com/rad-in-action/delphi-labs

Episode 01: Simple Calculator Service - videowhitepapersource code

Both server and client projects compile fine, however if you run the server or the client, you would get "EIPAbstractError" exception with "No peer with the interface with guid [...] has been registered".

Where is this error coming from? In the example we are using DataSnap TCP/IP connectivity, which is internally implemented using Indy framework. The Indy implementation has changed between XE and XE3 versions, so there are little changes needed. On the server it is necessary to add "IPPeerServer" to the interface "uses" clause in the "ServerContainerUnit1" and on client we need to add "IPPeerClient" to the interface "uses" clause in the "ClientModuleUnit1". In fact the IDE can add these entries automatically. Just put the cursor somewhere in the "uses" clause of these two files and when you press "Enter" correct entries will be added for You! Cool:-)

I have also added a project group file "SimpleCalcGrp" and updated forms’ captions to "XE3".

Episode 02: Multitier Database Application - videowhitepapersource code

Two changes to source code here. One is the same as in Episode 01 and involves adding missing "IPPeerServer" and "IPPeerClient" units to "uses" clauses in the server and in the client project. The second change introduced was changing the database connection name in the TSQLConnection component on the server from "IBEMPLOYEE" to "EMPLOYEE". Delphi XE3 comes with a preconfigured "EMPLOYEE" connection to a sample InterBase database, so you can just open the project group, build all, run server and then run client and it should work "as is". A true multitier database application!

 

Episode 03: Server Methods Lifecycle - video 1 and video 2whitepapersource code

The third tutorial explores the different server methods instances lifecycle options. Understanding lifecycle of server side objects is critical to DataSnap architecture. I have just added missing "IPPeerServer" and "IPPeerClient" entries and updated forms’ captions and sizes. Below is a screenshot from a running demo with one server app and two clients invoking server methods on server objects with different lifecycle options set.

Episode 04: Testing DataSnap Server in Data Explorer - videowhitepapersource code

Data Explorer has been completely redesigned in Delphi XE3 and is now much more powerful. The demo contains just a server project, which listens to clients on TCP/IP port 211 and HTTP port 8080.

The only changes to original XE source code was to add "IPPeerServer" unit and updating form caption.

Episode 05: Authentication and Authorization - video 1 and video 2whitepapersource code

Standard changes. Adding "IPPeerServer" and "IPPeerClient" units plus form captions updated.

Episode 06: Transport Filters - videowhitepapersource code

Beyond standard changes including adding "IPPeerServer" and "IPPeerClient" units and modifying forms’ captions, I have also added one line to the client "OnClick" event handler to make sure that the connection is open before calling a server method.

with SQLConnection1 do if not Connected then Open;

Transport filters is a very elegant feature!

Episode 07: REST Web Application - videowhitepapersource code

The "REST Web Application" is a walk through different parts of a standard Delphi REST app generated with the wizard with default options. In this tutorial the "Add" server method is implemented and dynamically generated JavaScript proxies are shown.

Between XE and XE3 there were some refactorings performed. One of them was moving "TDSSessionManager" class from "DSService" unit to "DataSnap.DSSession". As the consequence this is a change that has to be done to compile the XE version of DataSnap REST server application in XE3: to change the "uses" clause of the main form unit and replace "DSService" with "DataSnap.DSSession". That is the only change except for updating main form’s caption to "XE3".

Episode 08 (part 1): WebBroker jQuery Mobile "Boilerplate" - video 1 and video 2whitepapersource code

Next two episodes in the serie has been inspired by the presentation that I had to prepare for students in the University College of London (UCL). I wanted to do something new and interesting. This tutorial is not exactly DataSnap, it is more generic. DataSnap servers can be implemented as either standalone executables or hosted in a web server. "WebBroker" is the underlying technology of all web server project types in Delphi: "DataSnap REST Application", "DataSnap WebBroker Application", "SOAP Server Application" or just a custom HTTP "Web Server Application".

Two years ago jQuery Mobile was not as widely known as it is today, but it definitely had its already high "coolness" factor. In this demo I have combined "jQuery Mobile Boilerplate" with a generic Delphi WebBroker application as a starting point to the next tutorial.

The Delphi XE version of the demo compiles and works with no modifications in XE3, however two years ago jQueryMobile framework was still in beta and today the latest stable version is "1.2.0 Final". I have updated the demo project to reference the latest jQuery 1.8.2 and jQuery Mobile 1.2.0.

Episode 08 (part 2): jQuery Mobile Web Frontend - video 1video 2video 3whitepapersource code

This is logically the second part of "WebBroker jQueryMobile Boilerplate" demo. Now that we know how to setup a generic Delphi WebBroker HTTP server application to serve jQuery Mobile markup, we can go one step further and display data from a database instead of a static content.

This episode demonstrates how generate data-driven jQueryMobile web pages generating HTML markup dynamically in Delphi code.

The demo source code contains a project group with two projects. The first project is a DataSnap standalone server ("DataServerApp") with a database connection and architecture very similar to the server from the "Episode 2: Multitier Database Application". The second project acts as a client to it and is an extended version of the jQuery Mobile Boilerplate project from the previous episode.

In the the data server project it was necessary to add "IPPeerServer" unit to the "uses" clause of the "ServerContainerUnit2" file and also to change the "ConnectionName" of the SQL connection component to "EMPLOYEE" (the same changes as in Episode 2).

The changes to the client webbroker project ("jQueryMobileApp") involved updating the reference to jQueryMobile version used in the same way as in the "boilerplate" episode previously.

I have also updated the "About" page markup embedded in Delphi code to display up-to-date information about Delphi, InterBase and jQueryMobile versions used.

The demo app has been updated and reuploaded to Amazon EC2 demo instance. Check it out yourself on your mobile device web browser at http://79.125.25.31:8080

This demo shows how to generate jQuery read-only markup. If you are interested how to use DataSnap REST and jQuery Mobile for interactive web pages, check out my later blog post here.

Episode 09: DataSnap Server implemented as Windows Service - videowhitepapersource code

The only change needed to update this demo was a standard addition of "IPPeerServer" unit to the server container unit. The project compiles fine and installs and run with no problems under Windows 8.

Episode 10: Passing "Plain Old Delphi Objects" - videowhitepapersource code

Standard changes only: added "IPPeerServer" and "IPPeerClient" units, created a project group for convinience and also updated form captions.

Episode 11: Callbacks - video 1video 2video 3whitepapersource code

This is the last episode from the original DelphiLabs DataSnap XE serie and it only touches on one of the most powerful features of DataSnap - callbacks.

In the server project I have only changed the caption of the form and added "IPPeerServer" in a standard way.

The client project is slightly more involving. If you put a cursor inside the "uses" clause of the "FormClientUnit" you will see that not only "IPPeerClient" is added, but also "IPPeerServer". That is OK. In this scenario the server project acts only as a bridge and "clients" are both sending and receiving messages, so they are acting as both clients and servers. It is also necessary to add "DataSnap.DSSession" unit to the "uses" clause, because "TDSTunnelSession" class that is used in code has been moved there. The last change was to add the code to open "SQLConnection1" in the form’s OnCreate event.

When I was reviewing all these episodes - I have realized that it was not only a lot of work, but also great fun to explore this super elegant technology, so…

Delphi Labs will return!


Delphi XE2 Boilerplate DataSnap Server and jQueryMobile Client

Written by Paweł Głowacki on . Posted in ENTERPRISE

    Introduction

In this article we are going to build a sample system consisting of a Delphi DataSnap REST server application and jQuery Mobile web client.

We are going to use Delphi XE2 and jQueryMobile 1.0.1, which is the latest versions available at the time of writing this article (January 2012).

    Architecture

Embarcadero DataSnap is an enterprise framework; very much like JEE, CORBA, SOAP and similar client/server, distributes, multitier architectures. DataSnap is a feature of enterprise and higher versions of Embarcadero Delphi, C++Builder and RAD Studio. With DataSnap it is possible to build scalable, fault-tolerant, multitier systems with native, compiled server application and wide variety of native, managed and web clients communicating with server using different communication protocols.

In this particular example we are going to focus on a server application implemented as native Delphi XE2 Windows standalone web server REST application and web browser-based client running inside of the HTML web page using popular jQueryMobile JavaScript library which makes it easy to create interactive web pages optimized for web browser running on smartphones.

    Building Delphi REST server web application with the wizard

Delphi XE2 provides special “DataSnap REST Application” wizard that generates a boilerplate project consisting of the following parts:

  • Server: native Delphi Windows DataSnap REST web server application
  • Client: JavaScript code embedded in HTML pages

Start Delphi XE2. Click on the “File/New/Other…” menu item. Inside the “New Items” dialog go to “Delphi Projects/DataSnap Server” category and “DataSnap REST Application” icon. Click on OK.

Hide image
Fig_NewItems_NewDelphiXE2_DataSnapRESTApp_Icon

In the first step of the “New DataSnap REST Application” wizard we need to decide how our web application must be implemented: as a standalone VCL/Console-based web server application that is a self-contained Windows executable containings both a web server and a web application, or as a DLL to be deployed to an ISAPI combatible web server. We are going to go for the first, default option which is a standalone WebBroker application.

Hide image
Click to see full-sized image

In the second step of the wizard we need to decide which HTTP port number we want to use and if we want to support the HTTPS. We are going to leave the default port “8080” and leave “HTTPS” option unchecked. It is always a good idea to click on a “Test Port” button to verify that it is not already in use on your machine. Click “OK” and “Next”.

expand view>>

Hide image
Click to see full-sized image

The third screen of the wizard is for choosing server features. Keep the features selected by default and also select “Server Module” feature.

expand view>>

Hide image
Click to see full-sized image

On the following wizard screen we can choose the base class for our server methods class. If you keep the default option of inheriting directly from “TComponent” you cannot really use the non-visual components like data base connections, queries or datasets.

expand view>>

Select “TDSServerModule” to inherit also the implementation of IAppServer interface on the server for DataSnap TClientDataSet-based clients.

Hide image
Click to see full-sized image

The last screen is the projection location. The last part of the name will be used as both a new folder and also the actual project name.

expand view>>

You can put this project anywhere. Enter “C:\Demos\DelphiJQueryMobileApp1” and click finish button.

Hide image
Click to see full-sized image

Select “Save All” and keep all default names for different units in the project.

expand view>>

Run the project now. It will first display the main form of the VCL Forms application that acts as a standalone web server and listens on a specified port.

Hide image
Fig_VCLWebServer_Form

Click on “Open Browser” button to start the web application and open it in the default web browser. I’m using Chrome, but it might be a different default web browser, like Internet Explorer or Firefox.

Hide image
Click to see full-sized image

We have selected “Authentication” and “Authorization” options in the wizard. This is the reason that we are presented with username and password text boxes. Currently there is no username or password check implemented, so we can just press on the “LOG IN” button to hide login controls and display “Reverse String” text box and button.

expand view>>

Hide image
Click to see full-sized image

Click on the button and notice that the contents of the edit box has been reversed and that the “page loaded at” information does not change.

expand view>>

Hide image
Click to see full-sized image

Pressing on the “ReverseString” button makes the JavaScript code embedded in the “ReverseString.html” code to invoke the “ReverseString” method implemented in Delphi code in the “TServerMethods1.ReverseString” method. The web page is not reloaded because the call to the server method has been made asynchronously using AJAX.

expand view>>

We can verify that clicking on the button in the web browser calls server side functionality implemented in native Delphi code by placing a breakpoint inside the “ReverseString” method and re-running the web application with debugging.

Close the web browser and close the web server application.

Open “ServerMethodsUnit1” unit and click in the gutter just in the line 30, where the reverse string functionality is implemented.

Now press F9 or just click on the green arrow “run” button with a little red-black bug on it to run the application with debugging.

Open the browser window and click on the “LOG IN” button. When you click on “Reverse String” button you should see that your breakpoint stopped the execution of the program. Click on “Run” button again to resume the program.

The ability to debug our server-side Delphi logic is very powerful. You can implement your application server and data access logic in powerful Delphi code and you can use HTML5, CSS3 and jQuery to implement the client side user interface and server access logic.

Hide image
Click to see full-sized image

The “REST Web Application” wizard has created for us a complete system made of the HTML and JavaScript client and Delphi server.

expand view>>

Writing Delphi code is easy and full of fun. Writing a client in JavaScript is more involving and more error prone. This is why we are going to focus on improving the client side of our system.

In order to simplify our web client JavaScript code we are going to use the “jQuery” – one of the most popular libraries for JavaScript programming. On top of the jQuery library we are also going to use “jQuery Mobile” library that is making sure that our web pages are optimized for all major web browsers running on contemporary smartphones.

There are two ways to reference jQuery in our web application. We can put a reference to jQuery sources hosted in the cloud by many vendors including Google, or we can physically add jQuery JavaScript source files to our project. For performance reasons you might want to use the first option, but for demontration reasons it could be a good idea to add referenced files physically to be able to use them while offline.

You can download the jQueryMobile from http://jquerymobile.com/. At the time of this writing the latest version of jQueryMobile is 1.0.1.

The next step is to start using jQuery in our JavaScript client code. With jQuery the JavaScript source code can be more compact and readable.

The goal of this article is to create a standard, boliler-plate “Delphi REST Application” with a simple jQueryMobile-enabled web client.

To keep this article simple, I’m not going to modify the server implementation, and will provide a very simple jQueryMobile web page that is going to use the server side functionality – our “Echo” and “ReverseString” test methods generated by the wizard.

    Understanding “ReverseString” web page code

Before doing any changes to the web application that was generated by the wizard, let’s have a look how the client side logic is currently implemented.

Our web page consists primarily of HTML markup and some JavaScript code. Before we can fully understand this code, we need to pay special attention to identifiers in code with the “#” prefix. These are so called “transparent tags” and are replaced with a dynamically generated content inside the “TWebModule1.ServerFunctionInvokerHTMLTag” event that is fired for every transparent tag encountered during the preprocessing of the web page.

If we check the implementation of this method inside the “WebModuleUni1” file, we can see for example that “serverfunctionjs” transparent tag is replaced in code with “js/serverfunctions.js”. There are also other tags that are being replaced.

Let’s first have a look at the JavaScript code that is executed when the “Reverse String” button is pressed.

With the authorization and authentication code stripped out for readability, this JavaScript code looks like this.

function onReverseStringClick()
{
  var valueField = document.getElementById('valueField');
  var s = serverMethods().ReverseString(valueField.value);
  valueField.value = s.result;
}

In the first line of code we assign the contents of the input element with a class “valueField” attached to it. The contents of this input control will be sent to the server side method as a parameter to “ReverseString” server method call.

The second line of code is where the actual call to the Delphi server-side code is made. The “serverMethods()” is a utility function that is defined in the same script as the following and it returns - configured with connection information – a client-side JavaScript object that implements the same functions as the server-side class.

function serverMethods()
{
  return new <#classname>(connectionInfo);
}

The transparent tag “classname” is resolved before this call happens with the actual name of a server methods class using a call to “ServerMethodsUnit1.TServerMethods1.ClassName”, which is in our case “TServerMethods1” string.

The JavaScript definition of this class is located in the “ServerFunctions.js” source file, which is generated automatically with the DataSnap proxy generator from a running Delphi DataSnap server in the “TWebModule1.WebFileDispatcher1BeforeDispatch” event handler.

The automatically generated JavaScript unit contains references to a couple of “Delphi DataSnap Framework“ JavaScript library files that needs to be deployed with the JavaScript client code.

This class looks like this. I have stripped out calls to “EchoString” for readability.

function TServerMethods1(connectionInfo)
{
  this.executor = new ServerFunctionExecutor("TServerMethods1",connectionInfo);
  /*
   * @param Value [in] - Type on server: string
   * @return result - Type on server: string
   */
  this.ReverseString = function(Value) {
    var returnObject = this.executor.executeMethod("ReverseString", "GET", [Value], arguments[1], true, arguments[2], arguments[3]);
    if (arguments[1] == null) {
      if (returnObject != null && returnObject.result != null && isArray(returnObject.result)) {
        var resultArray = returnObject.result;
        var resultObject = new Object();
        resultObject.Value = Value;
        resultObject.result = resultArray[0];
        return resultObject;
      }
      return returnObject;
    }
  };

  this.ReverseString_URL = function(Value) {
    return this.executor.getMethodURL("ReverseString", "GET", [Value], arguments[1])[0];
  };
} 

The “TServerMethods1” class acts as a proxy to the remaining JavaScript code and hides the actual implementation of the HTTP GET call to a remote DataSnap server application that knows how to talk HTTP.

The actual implementation of the “ServerFunctionExecutor” is not the most important now. It comes with the “Delphi DataSnap Framework” JavaScript library files and is implemented in “ServerFunctionExecutor.js” source file.

It is important to notice that the “TServerMethods1” constructor expects as a parameter a “connectionInfo” object that is later passed to “ServerFunctionExecutor” constructor.

The “connectionInfo” variable is defined in the “connection.js” source file and it is initialized in the web page “onLoad()” event with the call to “setConnection” method.

// “ReverseString.html”

function onLoad()
{
  // …
  setConnection('<#host>', '<#port>', '<#urlpath>');
  // …
}

The “setConnection” function is a simple utility function defined like this in the “connection.js” file

 
// “connection.js”

var connectionInfo;

function setConnection(host, port, urlPath)
{
  connectionInfo = {"host":host,"port":port,"authentication":null,"pathPrefix":urlPath};
}

Transparent tags “host”, “port” and “urlpath” are replaced before this html page is dispatched with the actual server-side values from in the Delphi server code from the “Request” object that encapsulates current HTTP request.

procedure TWebModule1.ServerFunctionInvokerHTMLTag(Sender: TObject; Tag: TTag;
  const TagString: string; TagParams: TStrings; var ReplaceText: string);
begin
  if SameText(TagString, 'urlpath') then
    ReplaceText := string(Request.InternalScriptName)
  else if SameText(TagString, 'port') then
    ReplaceText := IntToStr(Request.ServerPort)
  else if SameText(TagString, 'host') then
    ReplaceText := string(Request.Host) 

// …

Summarizing, in order to call server-side function implemented in Delphi code from our JavaScript we need to call “serverMethods” function that returns properly initialized “TServerMethods1” instance that can be used to call the server side functionality.

Let’s move back to the “onReverseStringClick” event hander code. With jQuery there is no need to use built-in JavaScript function “document.getElementById”, we can simplify the code by using jQuery “$” reference.

jQuery also helps to make a cleaner separation between the HTML markup that defines how the web page looks from the JavaScript code that controls what the web page does.

Currently the body of our web page looks like this:

<body onload="onLoad()">
 // … 
    <div id="contentdiv" class="contentdiv" style="display:none">
      <table>
        <tr>
          <td><input id="valueField" class="loginField" type="text" value="A B C" /></td>
          <td><button onclick='javascript:onReverseStringClick();'>ReverseString</button></td>
        </tr>
      </table>
    </div>
</body>
</html>

Notice that the “button” tag has a “onReverseStringClick()” event handler assigned to its “onclick” event.

The jQuery library uses the “$(document).ready” event handler for centralizing all the event handling code, we can remove the code that assigns JavaScript code to button’s “onclick” event and move it to he “ready” function, so the resulting code is cleaner. In order to be able to reference the button tag in code, we need to add an identifier to it. For example “btn_Reverse”.

<body onload="onLoad()">
 // … 
    <div id="contentdiv" class="contentdiv" style="display:none">
      <table>
        <tr>
          <td><input id="valueField" class="loginField" type="text" value="A B C" /></td>
          <td><button id="btn_Reverse">ReverseString</button></td>
        </tr>
      </table>
    </div>
</body>
</html>

Now that we understand what’s going on in this code, we can start with implementing our own, jQueryMobile-enabled, version of this page.

    Adding a boilerplate jQueryMobile page to our application

The jQueryMobile documentation comes with an example of a “boilerplate” page that represents a “hello world” starting point for any other, more complex page.

The contents of this page - as of version 1.0.1 - is the following:

<!DOCTYPE html> 
<html> 
    <head> 
    <title>My Page</title> 
    <meta name="viewport" content="width=device-width, initial-scale=1"> 
    <link rel="stylesheet" href="http://code.jquery.com/mobile/1.0.1/jquery.mobile-1.0.1.min.css" />
    <script src="http://code.jquery.com/jquery-1.6.4.min.js"></script>
    <script src="http://code.jquery.com/mobile/1.0.1/jquery.mobile-1.0.1.min.js"></script>
</head> 
<body> 

<div data-role="page">

    <div data-role="header">
         <h1>My Title</h1>
    </div><!-- /header -->

    <div data-role="content">    
         <p>Hello world</p>          
    </div><!-- /content -->

</div><!-- /page -->

</body>
</html>

In this step I’m going to add to the main web module of our Delphi REST application an additional “TPageProducer” component that is going to be responsible for generating the contents of a jQueryMobile boilerplate code.

Open “WebModuleUnit1” unit and drop a new instance of “TPageProducer” component to the main web module of our application. Rename it to “ppBoilerplate”.

Open the “HTMLDoc” property of the “ppBoilerplate” component and paste the HTML boilerplate markup into the editor. Click OK.

Hide image
Click to see full-sized image

expand view>>

This is the alternative way of providing the page markup. You can either use “HTMLFile” property to point to a physical HTML file, like the two other producer components, or directly enter the markup into the HTMLDoc property, so it is stored inside the DFM file and there is no need for an actual HTML file. This two properties are mutually exclusive, so setting one, automatically clears the other.

If we run the application now, we can see that it still displays the “ReverseString” page and not the contents from our new page producer.

We need to change the default page producer for our application, so the our jQueryMobile boilerplate markup is served to a browser.

In order to do this we need to change the implementation of the web module default action handler.

Click somewhere on the surface of the “WebModule1” to make sure that it is selected in the Object Inspector.

Hide image
WebActionItems prop of WebModule1

Select the “DefaultAction” item from the list of actions. Click on “Events” tab in the Object Inspector. When you click on the “OnAction” event you will see the following code in the editor:

procedure TWebModule1.WebModuleDefaultAction(Sender: TObject;
  Request: TWebRequest; Response: TWebResponse; var Handled: Boolean);
begin
  if (Request.InternalPathInfo = '') or (Request.InternalPathInfo = '/')then
    Response.Content := ReverseString.Content
  else
    Response.SendRedirect(Request.InternalScriptName + '/');
end;

We can see from this code that for every HTTP request arriving to our Delphi web application, the contents of the “ReverseString” page producer component is assigned to the “Content” property of the “Response” object that encapsulates the HTTP response that is eventually going to be sent to the web browser.

Let’s just replace “ReverseString” with the name of our page producer “ppBoilerplate”.

procedure TWebModule1.WebModuleDefaultAction(Sender: TObject;
  Request: TWebRequest; Response: TWebResponse; var Handled: Boolean);
begin
  if (Request.InternalPathInfo = '') or (Request.InternalPathInfo = '/')then
    Response.Content := ppBoilerplate.Content
  else
    Response.SendRedirect(Request.InternalScriptName + '/');
end;

Click on “Save All” and run the web application.

Now we should see the jQueryMobile 1.0.1 boilerplate page in the web browser.

Hide image
Click to see full-sized image

The next step is to add interactions between the jQuery code in the web page and our Delphi server methods.

expand view>>

    Calling DataSnap server methods from jQueryMobile code

Now we are going to enhance our boilerplate jQueryMobile web page and add to it a possibility to call server-side functions implemented in Delphi code.

That would only require modifying the value of the “HTMLDoc” property. You can conventiently open this property in the code editor clicking on the “Code Editor…” button in the bottom left corner of the string list editor.

Just replace the contents of the “HTMLDoc” property with the following code:

<!DOCTYPE html>
<html>
<head>
<title>Delphi DataSnap jQueryMobile Template</title>

<meta name="viewport" content="width=device-width, initial-scale=1">

<link rel="stylesheet" href="http://code.jquery.com/mobile/1.0.1/jquery.mobile-1.0.1.min.css" />
<script src="http://code.jquery.com/jquery-1.6.4.min.js"></script>
<script src="http://code.jquery.com/mobile/1.0.1/jquery.mobile-1.0.1.min.js"></script>
<script type="text/javascript" src="js/base64.js"></script>
<script type="text/javascript" src="js/json2.js"></script>
<script type="text/javascript" src="js/serverfunctionexecutor.js"></script>
<script type="text/javascript" src="js/connection.js"></script>
<script type="text/javascript" src="js/serverfunctions.js"></script>

<script type="text/javascript">

function serverMethods()
{
  return new TServerMethods1(connectionInfo);
}

$(document).ready(function() {

    $('#btn_reverse').click (function (){
      var value = $('#valueField').val();
      var value = serverMethods().ReverseString(value).result;
      $('#valueField').val(value);
    });

    $('#btn_echo').click (function (){
      var value = $('#echoField').val();
      var value = serverMethods().EchoString(value).result;
      $('#echoField').val(value);
    });

});

</script>
</head>

<body>

<div data-role="page" id="main_page" data-theme="a">

  <div data-role="header">
    <h1>Delphi and jQueryMobile</h1>
  </div><!-- /header -->

  <div data-role="content">
    <p>Click "Reverse" button to invoke Delphi REST server method to reverse the content of the edit box</p>
    <input id="valueField" type="text" value="A B C" />
    <a href="#" data-role="button" id="btn_reverse">Reverse</a>
    <p>View the second <a href="#second_page">page</a></p>
  </div><!-- /content -->

  <div data-role="footer">
    <h4>Page Footer</h4>
  </div><!-- /footer -->

</div><!-- /page -->

<!-- Start of second page -->
<div data-role="page" id="second_page">

  <div data-role="header">
    <h1>This is the second page</h1>
  </div><!-- /header -->

  <div data-role="content">
    <p>I'm the second in the source order so I'm hidden when the page loads. I'm just shown if a link that references my ID is beeing clicked.</p>
    <p>Click button</p>
    <input id="echoField" type="text" value="Hi" />
    <a href="#" data-role="button" id="btn_echo">Echo</a>
    <p><a href="#main_page">Back to main page</a></p>
  </div><!-- /content -->

  <div data-role="footer">
    <h4>Second Page Footer</h4>
  </div><!-- /footer -->

</div><!-- /page -->

</body>
</html>

Save all and run the application. Depending on your browser you should see a page that contains a “Reverse String” button. There is also a link that makes it possible to jump to the second page with “echoing” functionality.

Hide image
Click to see full-sized image Hide image
Click to see full-sized image

That’s it. The source code for this template application can be downloaded from Embarcadero Code Central.

expand view>>

expand view>>

You can optionally remove from this boilerplate applications all the unused code, but that’s something I’m leaving to the reader.

    Summary

In this article we have seen how to start building enterprise, multitier systems using Embarcadero Delphi XE2 and jQueryMobile.

RAD Studio XE2 includes many interesting and powerful features like 64-bit native Delphi compilation and the new FireMonkey framework for building great looking native application on multiple operating systems. The DataSnap framework is often overlooked by Delphi and C++Builder developers. The DataSnap architecture is the perfect choice for building server-side applications for any clients. Currently there are two main kinds of client applications that can work with DataSnap servers: native applications and web applications.

For native clients I would choose Delphi FireMonkey, for web clients I would go for the pure HTML5 solution. jQuery and jQueryMobile are great choices to simplify the process of writing web clients in JavaScript code.

    References


DataSnap connectivity for iOS using Delphi XE2 and FireMonkey

Written by Anders Ohlsson on . Posted in ENTERPRISE

    Problem: Data connectivity for iOS?

Let's start with a disclaimer. There is currently no data connectivity in FireMonkey for iOS in the currently shipping version of RAD Studio XE2. Data connectivity for iOS is of course very important, and it is being planned, prioritized and road mapped. Please see edn.embarcadero.com and blogs.embarcadero.com for more information about roadmaps.

    Solution: Mobile DataSnap connector for ObjectiveC!

This article discusses how you can use the ObjectiveC mobile DataSnap Connector that does ship with RAD Studio XE2.

First, a shout-out. Phil Hess was of great assistance in helping me getting a grasp on ObjectiveC versus Pascal. He parsed all the ObjectiveC header files for the mobile DataSnap connector and sent them to me. The result is a collection of about 70 files. The files are as follows:

  • dsproxybase (directory of 56 files)
  • sbjson (directory of 13 files)
  • DSProxyBase.pas
  • AnonClassDefinitionsDsproxybase.pas
  • AnonClassDefinitionsSbjson.pas

    Parsing the headers of the ObjectiveC connector

Phil took the existing ObjectiveC to FreePascal parser from here:

http://web.me.com/macpgmr/ObjP/Xcode4/iOS_Parsing_Status.html

He had to patch it to fix a few things in order to parse SBJson and DSProxyBase properly. The resulting parsed Pascal files then needed a few edits to work around thigs that the parser does not yet handle.

Instead of providing all the steps on how to reproduce the parsing, I'm simply providing the files as parsed and edited by Phil Hess here, along with the entire project (server and client) as demonstrated in this article:

http://cc.embarcadero.com/item/28579

    Writing the DataSnap server

We will start by creating a DataSnap server by selecting File | New | Other | Delphi Projects | DataSnap Server | DataSnap REST Application.

Hide image
Click to see full-sized image

We go through the expert and select Stand-Alone VCL application in this case:

Hide image
Click to see full-sized image

We then select a port and test it:

Hide image
Click to see full-sized image

On the next screen we simply go with the defaults (sample methods and sample web files) for simplicity:

Hide image
Click to see full-sized image

On the next screen we'll pick TDataModule as the ancestor:

Hide image
Click to see full-sized image

We then finish up and save the project as EmployeeServer.dproj somewhere on disk.

    Adding our business logic

Now we need to add some actual useful methods that implement our business logic. In this case we will provide two main methods - GetRecordCount and GetRecord. We'll discuss GetRecords a bit later.

Let's first add a TSQLConnection and a TSQLDataSet to our ServerMethodsUnit1 (default name):

Hide image

We're simply going to connect to one of the demo databases that ship with RAD Studio XE2 (EMPLOYEE.GDB) in the Samples directory. The TSQLConnection and the TSQLDataSet will have the following parameters set, respectivelly:

Hide image
Hide image

Here's the declaration of our TServerMethods1 (default naming again). We add GetRecordsCount, GetRecord and GetRecords.

type
  TServerMethods1 = class(TDataModule)
    MyDB: TSQLConnection;
    MyDS: TSQLDataSet;
  private
    { Private declarations }
  public
    { Public declarations }
    function GetRecordCount: Integer;
    procedure GetRecord(RecNo: Integer; var FirstName, LastName: String);
    function GetRecords : TJSONArray;
  end;

The implementation of GetRecordCount is very simple as follows:

function TServerMethods1.GetRecordCount: Integer;
begin
  MyDS.Open;
  Result := MyDS.RecordCount;
  MyDS.Close;
end;

The implementation of GetRecord is also fairly simple. It's also very inefficient, but let's not get into that here... This is only done so that I can get a specific record by record number as a simple demo.

procedure TServerMethods1.GetRecord(RecNo: Integer; var FirstName, LastName: String);
var
  i: Integer;
begin
  MyDS.Open;
  for i := 0 to RecNo-1 do
    MyDS.Next;
  FirstName := MyDS.FieldByName('FIRST_NAME').AsString;
  LastName := MyDS.FieldByName('LAST_NAME').AsString;
  MyDS.Close;
end;

Finally, as a comparison, I have another method - GetRecords - that gives me all of the records as a TJSONArray. It also has a comment section that describes how to get the data back out of the array on the Win/Mac side (different code applies to iOS).

function TServerMethods1.GetRecords : TJSONArray;
var
  NewArr : TJSONArray;
  NewObj : TJSONObject;
  Val : TJSONValue;
  Pair : TJSONPair;
begin
  NewArr := TJSONArray.Create;
  MyDS.Open;
  while not MyDS.EOF do begin
    NewObj := TJSONObject.Create;
    NewObj.AddPair('LastName',MyDS.FieldByName('LAST_NAME').AsString);
    NewObj.AddPair('FirstName',MyDS.FieldByName('FIRST_NAME').AsString);
    NewArr.AddElement(NewObj);
    MyDS.Next;
  end;
  MyDS.Close;
  Result := NewArr;

  // You get the data back by doing this:
  // TJSONObject(NewArr.Get(index)).Get('LastName').JsonValue.Value;
  // TJSONObject(NewArr.Get(index)).Get('FirstName').JsonValue.Value;
end;

    Testing the DataSnap server

We can now compile and run the DataSnap server. It pops up on the screen as below:

Hide image
  

If we click the Open Browser button we get presented with the test harness. Notice that we haven't written a client yet. This is all done by the fact that we wrote the above server.

Hide image
Click to see full-sized image

Here we can expand the nodes for our actual server methods that do the real work of the server. Below GetRecordCount as executed (result is 42 records):

Hide image
Click to see full-sized image

GetRecord looks as follows. Notice that we provide the input value (RecNo = 0), and we get back Robert Nelson as our record data.

Hide image
Click to see full-sized image

Finally, GetRecords as executed (gives us all records):

Hide image
Click to see full-sized image

    Writing the iOS client

Now, on to writing the iOS client that will talk to our server. We create a new FireMonkey HD application for iOS. The UI part is the simple part here:

Hide image

Now we have to write the actual code behind the button that retrieves the data... Disclaimer: There may very well be much more elegant ways of writing this, especially the string conversions between FPC/ObjC and Delphi.

The first thing that is necessary at the top of the unit is the following directive to the FreePascal compiler in order to compile our FPC/ObjC type conversion code.

unit Unit1;

{$IFDEF FPC}
{$modeswitch ObjectiveC1}
{$ENDIF}

In the interface section we'll add iPhoneAll, SBJson and DSProxyBase. The iPhoneAll unit is part of FPC as shipping with RAD Studio XE2 and gets installed when you install the Mac parts for iOS development.

The SBJson and DSProxyBase units are the ones Phil parsed for me, and necessary in order to work with JSON and communicating with the DataSnap server.

interface

uses
  SysUtils, Types, UITypes, Classes, Variants, FMX_Types, FMX_Controls, FMX_Forms,
  FMX_Dialogs, FMX_Edit, FMX_Layouts, FMX_Memo, FMX_ListBox
{$IFDEF FPC}
  ,iPhoneAll, SBJson, DSProxyBase
{$ENDIF}
  ;

We'll declare a connection variable first:

var
  Connection : DSRESTConnection;

Next up, GetRecordCount:

function GetRecordCount: Integer;
var
  cmd : DSRESTCommand;
begin
  cmd := Connection.CreateCommand;

{$IFDEF FPC}
  cmd.setRequestType(GET);
  cmd.setText(NSSTR(PChar('TServerMethods1.GetRecordCount')));
  cmd.prepare(
    NSArray.arrayWithObjects(
      DSRESTParameterMetaData.parameterWithName_withDirection_withDBXType_withTypeName(
        NSSTR(PChar(String(''))),4{ReturnValue},Int32Type,NSSTR(PChar(String('Int32')))),
      nil
    )
  );
{$ENDIF}

  cmd.execute;

{$IFDEF FPC}
  Result := cmd.getParameterByIndex(0).getValue.GetAsInt32;
{$ENDIF}
end;

Let's break this one down. We declare a REST command variable first:

var
  cmd : DSRESTCommand;

We create the command:

  cmd := Connection.CreateCommand;

We then set the request type (REST request types include GET/POST/etc):

  cmd.setRequestType(GET);

Next, we set the text of the request. This is the fully qualified name of the method we're going to call on the DataSnap server:

  cmd.setText(NSSTR(PChar('TServerMethods1.GetRecordCount')));

We then prepare the command. Prepare takes one parameter. This parameter is an array of DSRESTParameterData. In this case the only parameter is the return value from GetRecords. Since it is a return parameter, it is nameless (empty string). We pass it as Int32Type. We also pass Int32 as a string as the last parameter.

  cmd.prepare(
    NSArray.arrayWithObjects(
      DSRESTParameterMetaData.parameterWithName_withDirection_withDBXType_withTypeName(
        NSSTR(PChar(String(''))),4{ReturnValue},Int32Type,NSSTR(PChar(String('Int32')))),
      nil
    )
  );

We then execute the command:

  cmd.execute;

Finally, we extract the return value and return it from our function:

  Result := cmd.getParameterByIndex(0).getValue.GetAsInt32;
end;

Much in the same way, GetRecord gets implemented as below. Notice that we now have one input parameter (RecNo), two output parameters (FirstName and LastName) and no return value (it's a procedure).

procedure GetRecord(RecNo: Integer; var FirstName, LastName: String);
var
  cmd : DSRESTCommand;
begin
  cmd := Connection.CreateCommand;

  cmd.setRequestType(GET);
  cmd.setText(NSSTR(PChar('TServerMethods1.GetRecord')));
  cmd.prepare(
    NSArray.arrayWithObjects(
      DSRESTParameterMetaData.parameterWithName_withDirection_withDBXType_withTypeName(
        NSSTR(PChar(String('RecNo'))),1{Input},Int32Type,NSSTR(PChar(String('Int32')))),
      DSRESTParameterMetaData.parameterWithName_withDirection_withDBXType_withTypeName(
        NSSTR(PChar(String('FirstName'))),2{Output},WideStringType,NSSTR(PChar(String('String')))),
      DSRESTParameterMetaData.parameterWithName_withDirection_withDBXType_withTypeName(
        NSSTR(PChar(String('LastName'))),2{Output},WideStringType,NSSTR(PChar(String('String')))),
      nil
    )
  );
  cmd.getParameterByIndex(0).getValue.setAsInt32(RecNo);

  cmd.execute;

  FirstName := String(PChar(cmd.getParameterByIndex(1).getValue.GetAsString.UTF8String));
  LastName := String(PChar(cmd.getParameterByIndex(2).getValue.GetAsString.UTF8String));
end;

Now for the "easy" part. Actually calling our methods and getting that data displayed in our client. Notice that the first thing we do here is create and set up the DataSnap connection. We provide the host address, the port, and the protocol to be used.

We then call GetRecordCount and iterate over all records to get them displayed in our UI.

procedure TForm1.GetDataButtonClick(Sender: TObject);
var
  i : Integer;
  FirstName, LastName : String;
begin
  Connection := DSRESTConnection.alloc.init;
  Connection.setConnectionTimeout(5);
  Connection.setHost(NSSTR(PChar(String(HostEdit.Text))));
  Connection.setPort(StrToInt(PortEdit.Text));
  Connection.setProtocol(NSSTR(PChar(String('http'))));

  for i := 0 to GetRecordCount-1 do begin
    GetRecord(i,FirstName,LastName);
    Memo1.Lines.Add(LastName+', '+FirstName);
  end;
end;

end.

    Compiling the client

Once the client is created. We run dpr2xcode to generate the extra information needed for Xcode. We then open the project in XCode.

    Testing the client

Actual screen shot of the client running on my iPhone 4:

Hide image
Click to see full-sized image

Just as a side-note, I also wrote code that allows you to test the client under Win32. Here's a screen shot of what the client looks like on Win32.

Hide image
Click to see full-sized image

    Comparing the implementation to Windows

If you have experience with JSON and DataSnap on Windows, then you'll be familiar with the following.

This is what GetRecordCount could be implemented as on the client side:

var
  GetRecordCountParams : array[0..0] of TDSRESTParameterMetaData =
    (
      (Name : ''; Direction : 4{ReturnValue}; DBXType : TDBXDataTypes.Int32Type; TypeName : 'Int32')
    );

function GetRecordCount: Integer;
var
  cmd : DSRESTCommand;
begin
  cmd := Connection.CreateCommand;
  cmd.RequestType := 'GET';
  cmd.Text := 'TServerMethods1.GetRecordCount';
  cmd.prepare(GetRecordCountParams);
  cmd.execute;
  Result := cmd.Parameters[0].Value.AsInt32;
end;

GetRecord could be implemented as such:

var
  GetRecordParams : array[0..2] of TDSRESTParameterMetaData =
    (
      (Name : 'RecNo'; Direction : 1{Input}; DBXType : TDBXDataTypes.Int32Type; TypeName : 'Int32'),
      (Name : 'FirstName'; Direction : 2{Output}; DBXType : TDBXDataTypes.WideStringType; TypeName : 'String'),
      (Name : 'LastName'; Direction : 2{Output}; DBXType : TDBXDataTypes.WideStringType; TypeName : 'String')
    );

procedure GetRecord(RecNo: Integer; var FirstName, LastName: String);
var
  cmd : DSRESTCommand;
begin
  cmd := Connection.CreateCommand;
  cmd.RequestType := 'GET';
  cmd.Text := 'TServerMethods1.GetRecord';
  cmd.prepare(GetRecordParams);
  cmd.Parameters[0].Value.SetInt32(RecNo);
  cmd.execute;

  FirstName := cmd.Parameters[1].Value.AsString;
  LastName := cmd.Parameters[2].Value.AsString;
end;

Finally, the button event handler that gets the number of records and iterates over the dataset to get all records looks almost exactly the same:

procedure TForm1.GetDataButtonClick(Sender: TObject);
var
  i : Integer;
  FirstName, LastName : String;
begin
  Connection := TDSRestConnection.Create(Self);
  Connection.Host := HostEdit.Text;
  Connection.Port := StrToInt(PortEdit.Text);
  Connection.Protocol := 'http';

  for i := 0 to GetRecordCount-1 do begin
    GetRecord(i,FirstName,LastName);
    Memo1.Lines.Add(LastName+', '+FirstName);
  end;
end;

    Resources

Download the whole project (server and client) from here:

http://cc.embarcadero.com/item/28579

ObjectiveC to FreePascal parser:

http://web.me.com/macpgmr/ObjP/Xcode4/iOS_Parsing_Status.html

    Contact

Please feel free to email me with feedback to aohlsson at embarcadero dot com.


Delphi Labs: DataSnap XE - jQueryMobile Web フロントエンド

Written by Chikako Yonezawa on . Posted in ENTERPRISE

    はじめに

このラボでは、jQuery Mobile JavaScript ライブラリを使用し、Delphi XE で、DanaSnap サーバーから取得したデータベースのデータを表示する Web アプリケーションを構築します。この記事を執筆している時点での jQuery Mobile は、開発中のアルファ版のため、現時点 (2011年3月) で利用可能な機能が、最終的なリリース版と異なる部分があるかもしれません。

この記事の中で、http://jquerymobile.com/demos/1.0a3/ から利用可能な jQuery Mobile Alpha 3 リリースを使用しています。

このチュートリアルの最終目的は、基となる InterBase XE のサンプル “Employee” データベースのデータを提供する Delphi XE DataSnap スタンドアロン データサーバーと、データを表示するために jQuery Mobile を使用する WebBroker Delphi XE スタンドアロン Web アプリケーションのクライアントから構成される、多層で、スケーラブルな DataSnap システムのサンプルの作成です。

このチュートリアルの前に、以前の Delphi Labs の記事で説明した “WebBroker jQueryMobile ボイラープレート” (http://edn.embarcadero.com/article/41325) があります。

Hide image
Click to see full-sized image

expand view>>

    jQueryMobile を理解する

ピュアな JavaScript でコーディングすることは、とても退屈で、この作業を簡単にするためのたくさんのフレームワークや、ライブラリがあります。最も人気のある JavaScript ライブラリの一つに「迅速で簡潔」で「迅速な Web 開発のための、HTML ドキュメントの走査、イベント処理、アニメーション、Ajax処理を簡素化」する、jQuery (http://jquery.com) があります。

jQuery の世界の中で、最も最近のプロジェクトの一つに、iPhone 、Android やその他のスマートフォン上で利用可能なWebブラウザ向けに最適化された、この人気のライブラリの “mobile” バージョンがあります。それは、“mobile” なルックアンドフィールを持った Web ページを構築するためのもので、 HTML5 や、CSS3 のような最新の Web 標準を使っている jQuery コアの上に位置するレイヤーです。

出発点となる “WebBroker jQueryMobile ボイラープレート” プロジェクトは、Embarcadero の Code Central (http://cc.embarcadero.com/item/28254) より使用することができます。jQueryMobile で動的に表示されるようにクライアントデータを提供する DataSnap サーバーアプリケーションを構築する時です。

    DataSnap サーバーアプリケーションを構築する

InterBase XE のサンプル “Employees” データベースの “Customers” テーブルからデータを読み込む、スタンドアロンの Delphi DataSnap VCL フォームアプリケーションサーバーを作成します。顧客のレコードが “会社名” でソートされるようにしますので、クライアント側でソートを実行する必要はありません。

プロジェクトマネージャのプロジェクトグループノード上でマウスの右ボタンをクリックし、“新規プロジェクトを追加” を選択し、「新規作成」ダイアログの “Delphi プロジェクト – DataSnap Server” から “DataSnap Server” アイコンをクリックします。

Hide image
Click to see full-sized image

expand view>>

ウィザードの最初の画面はデフォルトの “VCL フォームアプリケーション” のままとします。

2番目の画面では、サンプルメソッドを使用しないので、サンプルメソッドの生成のためのオプションのチェックは外します。

Hide image
Click to see full-sized image

expand view>>

次の画面は、デフォルトのポート番号のままにします。

最後に選択する内容はとても重要です。サーバーメソッドクラスのデフォルトの上位型ではなく、一番下の“TDSServerModule”を選択します。

Hide image
Click to see full-sized image

expand view>>

[完了] ボタンをクリックします。

メニューの [ファイル|すべて保存] をクリックします。メインフォームのユニットは “FormServerUnit” と命名し、サーバーコンテナユニットと、サーバーメソッドユニットはデフォルトの名前のままで保存します。また、プロジェクトは、”DataServerApp”、プロジェクトグループは “jQueryScalableSystem” と命名して保存します。

InterBase のサンプルデータベースに接続するための、接続設定が必要です。

「データエクスプローラ」内の “INTERBASE” ノードを選択します。そして、マウスの右ボタンをクリックして表示されるポップアップメニューより [新規接続を追加] を選択して名前を “IBEMPLOYEE” とします。

作成した “IBEMPLOYEE” を選び、マウスの右ボタンを押して表示されるポップアップメニューの中から [接続の変更] を選択します。

データベースファイルへのパスを入力します。InterBase XE をデフォルトのままインストールした場合は、“C:\Embarcadero\InterBase\examples\database\employee.ib” となります。

[テスト接続] のボタンをクリックし、データベースサーバーが起動しており、接続が確立できることを確認します。

データサーバーの実装は、コーディングを必要としません。選択した RDBMS インスタンスへのアクセスを提供する “TSQLConnection” コンポーネント、データベースから会社名でソートされた顧客情報を返却するクエリーが設定された “TSQLQuery” コンポーネント、クライアントアプリケーションから接続される “TDataSetProvider” コンポーネントを使用します。

サーバーメソッドモジュールに “TSQLConnection” コンポーネントを素早く追加するには、データエクスプローラから、サーバーメソッドユニット上へ、それをドラックします。これが、すべての必要なプロパティが自動的に設定される最速の方法です。代わりに、手作業で “TSQLConnection” コンポーネントを追加し、それに応じたプロパティを設定することもできます。

実際のデータベース接続情報は、データベース接続オブジェクトの “Params” プロパティに組み込まれています。

F6 キーを押し、”IDE インサイド” を表示します。そして、サーバーメソッドユニットへ ”TSQLQuery” コンポーネントを追加します。

すでにデータモジュール上にある接続オブジェクトを指すように、その “SQLConnection” プロパティを設定します。

“CUSTOMERS” テーブルから会社名でソートされた顧客情報を取得するための SQL 文を提供する必要があります。

データエクスプローラの “IBEMPLOYEE” データベース上で、マウスの右ボタンをクリックし、SQL 文を対話形式で作成するために「SQL ウィンドウ」を選択します。

右側にあるリストから “CUSTOMERS” テーブルを真ん中のメインウィンドウ上にドラッグします。

クエリーにすべてのフィールドを含ませるため、テーブル内のすべてのフィールドにチェックを付けます。

会社名を含んでいる “CUSTOMER” フィールドのソート順序として “Ascending” を選択します。

クエリーのテキストを選択し、マウスの右ボタンを押して表示されるポップアップメニューから、[コピー]を選択し、クリップボードにコピーします。

「SQL ウィンドウ」を閉じます。

Hide image
Click to see full-sized image

expand view>>

クエリー (TSQLQuery) コンポーネントの “SQL” プロパティを開き、そこにこの SQL 文を貼り付けます。

SQL 文が正しく動作するかを確認するために “Active” プロパティをクリックして “true” に設定できることを確認した後、このプロパティのチェックマークを外します(Falseにします)。

クエリーコンポーネントを “sqlqCustomersByCompanyName” とリネームします。

“TDataSetProvider” コンポーネントをモジュールへ追加します。

プロバイダ (TDataSetProvider) コンポーネントを “dspCustomersByCompanyName” とリネームします。

その “DataSet” プロパティに “sqlqCustomersByCompanyName” クエリーコンポーネントを設定します。

Hide image
Click to see full-sized image

expand view>>

これだけです! Delphi の DataSnap データサーバーの準備が整いました。

サーバーアプリケーションを実行させるために、緑色の三角アイコンをクリックします。

このチュートリアルが終わるまで、つまり、クライアントアプリケーションを開発し、その動作確認が終わるまで、サーバーは起動したままでなければなりません。

    DataSnap クライアントデータアクセスを追加する

今、コードエディタで 顧客データモジュールのコードを確認するのであれば、F12 キーを押して、データモジュールに非ビジュアルコンポーネントを追加することができるフォームデザイナから切り替えます。VCLフォームとは異なり、データモジュールは、それらのコンテンツを表示するようにデザインされていません。データアクセス機能のためのさまざまな非ビジュアルコンポーネントを配置することを可能にする設計時用の画面を持っています。

F6 キーを押して、「IDE インサイト」を表示します。 “TSQLConnection” とタイプしてそれを見つけたら、従業員データ用に接続を追加します。

わかりやすいように、この新しい接続を “DSSQLConnectionEmployees” と命名します。

この時、常に “LoginPrompt” プロパティのチェックを外すことは良い考えです。これにより “Login” ダイアログの表示は行われません。

“Driver” プロパティを “DataSnap”に変更します。これにより、 “Driver” プロパティの中のDataSnap接続のための固有のサブプロパティにアクセスできるようになります。同じコンピュータ上で、両方のアプリケーションを動作させているので “HostName” プロパティには “localhost” を指定します。実際のシナリオでは、動作しているマシンの IP アドレス、または、DNS名となります。

では、どのようにリモートデータサーバーへ接続するのでしょうか?

サーバー側では、VCL の “TClientDataSet” コンポーネントからのクライアントリクエストを受信する準備が整った “TDataSetProvider” コンポーネントがフォーム上にあります。

ウィザードで選んだサーバー側の実装の基底クラスは、MIDAS の時代からある “IAppServer” インターフェースを実装している “TDSServerModule” でした。

このインターフェースは、データプロバイダとクライアントデータセットのコンポーネントが、ネットワークを介してデータパケットを送受信するのに使用するメソッドを含んでいます。

このシナリオ内で、サーバーへアクセスするクライアントプロキシコードを生成する必要はありません。なぜなら、サーバー側の機能が “IAppServer” インターフェースによって、すでに定義されているからです。

  IAppServer = interface(IDispatch)
    ['{1AEFCC20-7A24-11D2-98B0-C69BEB4B5B6D}']
    function  AS_ApplyUpdates(const ProviderName: WideString; Delta: OleVariant;
                              MaxErrors: Integer; out ErrorCount: Integer; var OwnerData: OleVariant): OleVariant; safecall;
    function  AS_GetRecords(const ProviderName: WideString; Count: Integer; out RecsOut: Integer;
                            Options: Integer; const CommandText: WideString;
                            var Params: OleVariant; var OwnerData: OleVariant): OleVariant; safecall;
    function  AS_DataRequest(const ProviderName: WideString; Data: OleVariant): OleVariant; safecall;
    function  AS_GetProviderNames: OleVariant; safecall;
    function  AS_GetParams(const ProviderName: WideString; var OwnerData: OleVariant): OleVariant; safecall;
    function  AS_RowRequest(const ProviderName: WideString; Row: OleVariant; RequestType: Integer;
                            var OwnerData: OleVariant): OleVariant; safecall;
    procedure AS_Execute(const ProviderName: WideString; const CommandText: WideString;
                         var Params: OleVariant; var OwnerData: OleVariant); safecall;
  end;

データモジュールへ “TDSProviderConnection” コンポーネントを追加します。

私は、それを “DSProviderConnectionEmployees” とリネームしました。そして、その “SQLConnection” プロパティに、接続コンポーネント “DSSQLConnectionEmployees” を設定します。

ドロップダウンリストから直接、可能なサーバークラスの名前を選択することができたら良かったのですが、接続したいサーバー上のサーバークラスの実際の名前を入力する必要があります。この場合は “TServerMethodsIBEmployees” です。

データモジュールへ “TClientDataSet” コンポーネントを追加します。

その “RemoteServer” プロパティを “DSProviderConnectionEmployees” に設定し、すべてが正しく設定され、接続されているのであれば、 “ProviderName” プロパティのドロップダウンリストで、利用可能なデータセットプロバイダ名が表示されます。

サーバー上に複数のプロバイダ、および、サーバーへの同じ接続を共有する複数のプロバイダ接続コンポーネントを持つことができることに注意してください。プロバイダ名として、 “dspCustomersByCompanyName” を選択します。

クライアントデータセットコンポーネントを “cdsIBCustomers” にリネームし、 “Active” プロパティを “true” に設定し、すべてが正しく接続されるかを試し、その後 “false” に設定してください。設計時には “Active” のままにしたくはありません。データが必要になったときに有効 (“true”) にし、その後再び無効 (“false”) にします。

データアクセスコードを簡素化するために、クライアントデータセットフィールドを永続化するのは良い方法です。 “cdsIBCustomers” クライアントデータセットをダブルクリックして、 “項目の設定” ダイアログを表示します。ダイアログ内で、マウスの右ボタンをクリックし、 “すべてのフィールドを追加する” を選択します。

クライアントデータセットをオープンし、クライアントから顧客データを取得し、jQueryMobile 固有の機能を使用して HTML として整える “GetHtmbody” メソッドの実際の実装を提供する必要があります。

    Delphi で jQueryMobile コードを動的に生成する

このチュートリアルの主な焦点は、Delphi と DataSnap ですので、jQueryMobile の詳細については解説しません。

Web 上には興味深く有益な jQueryMobile リソースがたくさんあります。雑誌 “Practical Web Design” のFeb2011号に含まれていた http://net.tutsplus.com から、とても有益な “Simple development with jQuery Mobile (jQuery Mobile での簡単な開発)” マルチメディアチュートリアルを見つけました。jQueryMobile の基本情報を提供する他のリソースについては、このチュートリアル: http://miamicoder.com/2011/creating-a-website-using-jquery-mobile-part1/ で見つけました。

HTML ページの動的な作成を行うデータモジュールの最終バージョンのソースコードが以下にありますのでご覧ください。

以下のすべてのコードを選択し、Delphi のエディタ内にコピー&ペーストしてください。

unit DataModuleEmployeesUnit;

interface

uses
  SysUtils, Classes, DBXDataSnap, DBXCommon, DB, SqlExpr, DBClient, DSConnect;

type
  TCustomerData = record
    CUST_NO: Integer;
    CUSTOMER: String;
    CONTACT_FIRST: String;
    CONTACT_LAST: String;
    PHONE_NO: String;
    ADDRESS_LINE1: String;
    ADDRESS_LINE2: String;
    CITY: String;
    STATE_PROVINCE: String;
    COUNTRY: String;
    POSTAL_CODE: String;
    ON_HOLD: String;
  end;

  TDataModuleEmployees = class(TDataModule)
    DSSQLConnectionEmployees: TSQLConnection;
    DSProviderConnectionEmployees: TDSProviderConnection;
    cdsIBCustomers: TClientDataSet;
    cdsIBCustomersCUST_NO: TIntegerField;
    cdsIBCustomersCUSTOMER: TStringField;
    cdsIBCustomersPHONE_NO: TStringField;
    cdsIBCustomersCONTACT_LAST: TStringField;
    cdsIBCustomersCONTACT_FIRST: TStringField;
    cdsIBCustomersADDRESS_LINE1: TStringField;
    cdsIBCustomersADDRESS_LINE2: TStringField;
    cdsIBCustomersCITY: TStringField;
    cdsIBCustomersSTATE_PROVINCE: TStringField;
    cdsIBCustomersCOUNTRY: TStringField;
    cdsIBCustomersPOSTAL_CODE: TStringField;
    cdsIBCustomersON_HOLD: TStringField;
  private
    FCustDetailPages: string;
    function GetCustList: string;
    function GetCustDetailPages: string;
    procedure OutputCustomerDetailPage(const c: TCustomerData);
  public
    function GetHtmlBody: string;
  end;

//var
//  DataModuleEmployees: TDataModuleEmployees;

implementation

{$R *.dfm}

{ TDataModuleEmployees }

function TDataModuleEmployees.GetHtmlBody: string;
begin
  Result :=
 '<!-- Start of main page -->'
+'<div data-role="page" id="main">'

+'  <div data-role="header">'
+'       <h1>Delphi in the Cloud</h1>'
+'  </div><!-- /header -->'

+'  <div data-role="content">'

+'<h2>Welcome to DelphiLabs!</h2>'
+'<p>Press on the button below to find information'
+' about customers of a fictional company.</p>'

+'        <ul data-role="listview" data-theme="c" data-inset="true">'
+'          <li><a href="#customersMain"></a>Customers</li>'
+'          <li><a href="#about"></a>About</li>'
+'        </ul>'
+'  </div><!-- /content -->'

+'  <div data-role="footer">'
+'       <h6>DelphiLabs DataSnap Tutorial</h6>'
+'  </div><!-- /footer -->'
+'</div><!-- /page -->'

+'<!-- Start of about page -->'
+'<div data-role="page" id="about">'

+'  <div data-role="header">'
+'       <h1>About</h1>'
+'  </div><!-- /header -->'

+'  <div data-role="content">'

+'<h2>Welcome to DelphiLabs!</h2>'
+'<p>The page you just see has been created as a demo'
+' project for DelphiLabs "DataSnap" jQueryMobile tutorial'
+' and deployed to a virtual machine running in <a href="http://aws.amazon.com/">Amazon EC2</a>.</p>'
+'<p>Sample "Customers" data comes from <a href="http://www.embarcadero.com/products/interbase">InterBase XE</a> demo database'
+' that is accessed through <a href="http://www.embarcadero.com/products/delphi">Delphi XE</a> standalone DataSnap server application.</p>'
+'<p>The HTML markup that you just see right now has been generated dynamically in Delphi code'
+' using <a href="http://jquerymobile.com/2011/02/jquery-mobile-alpha-3-released/">jQueryMobile (alpha3)</a>.'
+'<hr/>'
+'<p>Visit <a href="http://www.embarcadero.com/rad-in-action/delphi-labs">'
+'Embarcadero RAD-in-Action Delphi-Labs page</a> for more details!</p>'
+'  </div><!-- /content -->'
+'  <div data-role="footer">'
+'       <h6>DelphiLabs DataSnap Tutorial</h6>'
+'  </div><!-- /footer -->'
+'</div><!-- /page -->'


+'<!-- Start of customers summary page -->'
+'<div data-role="page" id="customersMain">'

+'  <div data-role="header">'
+'       <h1>Customers</h1>'
+'  </div><!-- /header -->'

+'  <div data-role="content" data-theme="b">'

+ GetCustList

+'  </div><!-- /content -->'
+'  <div data-role="footer">'
+'       <h6>DelphiLabs DataSnap Tutorial</h6>'
+'  </div><!-- /footer -->'

+'</div><!-- /page -->'

+ GetCustDetailPages

end;

function TDataModuleEmployees.GetCustList: string;
var s: string; ch: char; c: TCustomerData;
begin
  s := '<ul data-role="listview" data-inset="true" data-theme="c" data-filter="true">';

  ch := ' ';

  cdsIBCustomers.Active := true;
  try
    while not cdsIBCustomers.Eof do
    begin
      c.CUST_NO := cdsIBCustomersCUST_NO.AsInteger;
      c.CUSTOMER := cdsIBCustomersCUSTOMER.AsString;
      c.CONTACT_FIRST := cdsIBCustomersCONTACT_FIRST.AsString;
      c.CONTACT_LAST := cdsIBCustomersCONTACT_LAST.AsString;
      c.PHONE_NO := cdsIBCustomersPHONE_NO.AsString;
      c.ADDRESS_LINE1 := cdsIBCustomersADDRESS_LINE1.AsString;
      c.ADDRESS_LINE2 := cdsIBCustomersADDRESS_LINE2.AsString;
      c.CITY := cdsIBCustomersCITY.AsString;
      c.STATE_PROVINCE := cdsIBCustomersSTATE_PROVINCE.AsString;
      c.COUNTRY := cdsIBCustomersCOUNTRY.AsString;
      c.POSTAL_CODE := cdsIBCustomersPOSTAL_CODE.AsString;
      c.ON_HOLD := cdsIBCustomersON_HOLD.AsString;


      if ch <> c.CUSTOMER[1] then
      begin
        ch := c.CUSTOMER[1];
        s := s + '<li data-role="list-divider" data-theme="b">' + ch + '</li>';
      end;

      s := s +
      '<li><a href="#cust' + IntToStr(c.CUST_NO) + '">' + cdsIBCustomersCUSTOMER.AsString + '</a></li>';

      OutputCustomerDetailPage(c);

      cdsIBCustomers.Next;
    end;
  finally
    cdsIBCustomers.Active := false;
  end;

  s := s + '</ul>';
  Result := s;

end;

function TDataModuleEmployees.GetCustDetailPages: string;
begin
  Result := FCustDetailPages;
end;

procedure TDataModuleEmployees.OutputCustomerDetailPage(const c: TCustomerData);
var s: string; aStatus: string;
begin
  if c.ON_HOLD <> '' then
    aStatus := '<B> (ON HOLD)</B>'
  else
    aStatus := '';

  s :=
 '<!-- Start of customer detail page -->'
+'<div data-role="page" id="cust' + IntToStr(c.CUST_NO) + '">'

+'  <div data-role="header">'
+'       <h1>Customer Details</h1>'
+'  </div><!-- /header -->'
+'  <div data-role="content">'

+'<p><h1>' + c.CUSTOMER + aStatus + '</h1></p>'

+'<div data-role="collapsible" data-theme="b">'
+'<h3>Contact</h3>'
+'<p><h2>' + c.CONTACT_FIRST + ' ' + c.CONTACT_LAST + '</h2></p>'
+'<p>' + c.PHONE_NO + '</p>'
+'</div>'

+'<div data-role="collapsible" data-theme="b">'
+'<h3>Address</h3>'
+'<p>' + c.ADDRESS_LINE1 + '</p>'
+'<p>' + c.ADDRESS_LINE2 + '</p>'
+'<p>' + c.CITY + '</p>'
+'<p>' + c.POSTAL_CODE + ' ' + c.CITY +'</p>'
+'<p>' + c.STATE_PROVINCE + '</p>'
+'<p><b>' + c.COUNTRY + '</b></p>'
+'</div>'

+'  </div><!-- /content -->'
+'  <div data-role="footer">'
+'       <h6>DelphiLabs DataSnap Tutorial</h6>'
+'  </div><!-- /footer -->'

+'</div><!-- /page -->';

  FCustDetailPages := FCustDetailPages + s;
end;

end.

この Web アプリケーションの 実行バージョンは、この URL: http://79.125.25.31:8080/ を通してアクセスすることができます。

私のChrome Webブラウザではこのように表示されます:

Hide image
Click to see full-sized image

expand view>>

“Customers” ボタンをクリックすると、会社名でソートされた顧客一覧リストへ遷移します。上にあるフィルタフィールドを確認してください。

Hide image
Click to see full-sized image

expand view>>

顧客名のところをクリックすると、その顧客の詳細を見ることができるでしょう。

Hide image
Click to see full-sized image

expand view>>

これだけ! Delphi XE で、モバイルデバイス用のあなた独自の見栄えのよい Web サイトを構築するために、ここで説明したテクノロジーを自由にご利用ください!

    まとめ

この Delphi Labs DataSnap チュートリアルの中で、モバイルデバイス上でデータベースデータを表示するための、多層でスケーラブルなシステムを構築する実用的な手順を紹介しました。データのソースとして InterBase XE データベース、データアクセスのための中間のスタンドアロン DataSnap サーバー、モバイル Webブラウザのために最適化されたモダンな jQueryMobile JavaScript ライブラリを使用し、データを表示するために WebBroker Web アプリケーションを使用しました。

jQueryMobile ライブラリのプレリリースバージョンを使用していることに注意してください。最終的な機能は、ここで説明したものと異なる可能性があります。

DataSnap は、Delphi, C++Builder, RAD Studio の Enterprise および Architect エディションの機能です。Starter および Professional エディションには含まれていません。

    参考資料

    若干の免責事項

“Delphi Labs” では、Delphi での開発にフォーカスしていますが、ここで説明されているアプリケーションのすべては、RAD Studio の一部である C++Builder でも構築可能です。

Delphi と C++Builder は、とても密接に統合されています。双方は、同一の基盤である統合開発環境 (IDE: Integrated Development Environment) 上の異なる「パーソナリティ」であり、VCL (Visual Component Library) を共有しています。

Delphi Pascal プログラミング言語を使用するのは、私個人の好みです。C++ をより好む方は、C++Builder XE (http://www.embarcadero.com/jp/products/cbuilder) でお試し下さい!


Учебный пример: DataSnap XE – Callbacks (меха...

Written by Vsevolod L1433 on . Posted in ENTERPRISE

    Введение

Целью данного учебного примера является создание максимально простых приложений DataSnap для клиента и сервера, которые будут использовать для взаимодействия функции обратного вызова.

В ходе выполнения примера мы будем использовать Delphi XE для создания простой системы, состоящей из клиента и сервера, для демонстрации работы функций обратного вызова. Серверное приложение будет играть роль коммуникационного центра (communication hub) для нескольких клиентских приложений, запущенных в сети. Это более реалистичный сценарий в сравнении с прямой посылкой уведомления из серверного приложения в клиентское. В большинстве случаев серверное приложение не имеет интерфейса пользователя, так что функции обратного вызова (callbacks) представляют собой отличный механизм взаимодействия между клиентами.

Первым шагом будет создание нового серверного приложения DataSnap с использованием мастера «DataSnap Server».

    Механизм обмена сообщениями

Наиболее общим механизмом обмена сообщениями в системах клиент/сервер является «запрос-ответ». Одно приложение («клиент») посылает сообщение («запрос») другому приложению в сети («сервер»), а сервер посылает обратно сообщение («ответ»).

Hide image

Во многих реальных приложениях бывает полезно иметь и обратный механизм, когда серверное приложение посылает сообщение («уведомление» - «notification») клиенту. Серверное приложение может сообщить клиенту, что произошло что-то интересное на сервере. Это называется «callback» («обратный вызов») – ситуация, когда сервер делает обратный (т.е. от сервера к клиенту в нарушение классического принципа) вызов клиента.

Hide image

Представьте себе чат-систему, когда множество клиентов, присоединенных к серверу, взаимодействуют друг с другом. Один клиент посылает сообщение серверу, а затем сервер пересылает это сообщение одному или нескольким клиентским приложениям.

Возможность сервера асинхронно посылать уведомления одному или нескольким клиентам является очень полезной в различных сценариях.

    Обратные вызовы каналы в DataSnap

Для того чтобы использовать механизм обратных вызовов в DataSnap, нужно определить класс функции обратного вызова, который является производным от класса «TDBXCallback» и переопределяет его виртуальный абстрактный метод «Execute». Вызов метода производится на сервере, а выполняется он на клиенте. Класс «TDBXCallback» определен в модуле «DBXJSON», как показано ниже (некоторые члены выброшены для большей читабельности):

unit DBXJSON;

interface

// …

type
  TDBXCallback = class abstract
  public
    function Execute(const Arg: TJSONValue): TJSONValue; overload; virtual; abstract;
    function Execute(Arg: TObject): TObject; overload; virtual; abstract;
    // …
  end;

В предыдущей версии DataSnap, который поставлялся с RAD Studio 2010, можно было использовать так называемые «лёгкие» («lightweight») функции обратного доступа. Экземпляр функции обратного вызова передавался в метод сервера, который затем выполнялся продолжительное время. Экземпляр, поступая в серверный метод в качестве параметра, использовался на сервере для вызова его метода «Execute» на клиенте в процессе работы серверного метода, например, для уведомления клиента о прогрессе долго длящейся операции.

В RAD Studio XE, самой последней на текущей момент версии, появились «тяжелые» («heavyweight» - тяжелые, солидные, мощные в противовес «lightweight» - лёгким, лёгковесным) механизмы обратного вызова. Они могут быть использованы в течение всего жизненного цикла клиентского приложения, а не только в течение работы серверного метода. Это открывает новые возможности для создания приложений различных типов. В оставшейся части этого учебного примера мы собираемся сфокусироваться на «тяжёлых» обратных вызовах, а для простоты будем ссылаться на них как на просто «обратные вызовы».

В архитектуре DataSnap обратные вызовы ассоциируются с «каналами» («channels»). В целом, может существовать несколько приложений, соединенных с сервером, причем каждый клиент может не содержать или содержать несколько функций обратного вызова. Сервер может «вещать» в канал так, что все функции обратного вызова каждого клиента, зарегистрированные в отдельном канале, получают это уведомление. Также возможно вызвать определенную функцию обратного вызова с использованием уникального идентификатора, заданного при регистрации функции обратного вызова на сервере. Таким образом можно организовать коммуникационную модель «peer-to-peer» (пиринговое взаимодействие, одноранговое взаимодействие, «точка-точка»).

Мы испытаем оба подхода: «вещание» в канал и использование отдельного callback-а.

Серверное приложение вызовет метод «Execute» на клиенте асинхронно. Это очень важно понять. Каждое приложение Delphi VCL Forms имеет главный поток выполнения, а в случае многопоточного приложения любые вызовы из других потоков, которые манипулируют графическим интерфейсом пользователя приложения, должны быть синхронизированы. Это именно та ситуация, когда используются обратные вызовы. Методы «Execute» обратного вызова вызывается в потоке, отличном от главного. Существуют различные способы синхронизации вызовов, но, возможно, самым простым путем является использование классового метода «TThread.Queue», который асинхронно вызывает блок кода в рамках главного потока.

    Реализация обратного вызова на стороне сервера

Наше серверное приложение будет тривиально простым. Функциональность обратного вызова встроена в компонент «DSServer», который является краеугольным камнем любого приложения DataSnap. В этом демонстрационном примере нам не нужно создавать серверные методы, т.к. мы собираемся осуществить взаимодействие между клиентскими приложениями с использованием функций обратного вызова.

Первым шагом является создание нового приложения сервера DataSnap с использованием мастера «DataSnap Server».

Выберите «File -> New -> Other» и в диалоге «New Items» кликните два раза на иконке «DataSnap Server» в категории «Delphi Projects -> DataSnap Server».

Hide image

На первой странице оставьте опцию «Project type» по-умолчанию как «VCL Forms Application».

Hide image

На второй странице мы оставим «TCP/IP» в качестве коммуникационного протокола, но снимем «галочку» для генерации «Server method class», т.к. нам этого ненужно для простой демонстрации обратных вызовов. Если вы оставите эту опцию для генерации серверных методов, то проблем не будет. Просто мы не будем их использовать.

Hide image

На третьей странице оставим значение порта TCP/IP по-умолчанию «211». Желательно всегда проверять, свободен ли порт, нажатием на «Test Port».

Т.к. ранее мы отменили опцию генерации серверного класса, нам не показана страница с просьбой выбрать базовый класс для нашего класса серверных методов.

Кликните на «Finish», и мастер создаст нам новый проект с двумя модулями: главной формой и серверным контейнером. В данном случае не будет модуля с серверными методами.

Кликните «File -> Save All».

Создайте новый папку для всех файлов данного примера, например, «C:\DataSnapLabs\SimpleCallbacks».

Сохраните главную форму приложения как «FormServerUnit» и оставьте имена по-умолчанию для модуля серверного контейнера, обычно, «ServerContainerUnit1».

Сохраните проект как «SimpleCallbackServer».

Выберите главную форму и в инспекторе объектов измените свойство «Name» на «FormServer», а свойство «Caption» на «Delphi Labs: DataSnap XE – Simple Callbacks – Server».

Серверная форма выглядит следующим образом:

Hide image

Откройте модуль серверного контейнера и проверьте, что на нем всего два компонента: «DSServer1» и «DSTCPServerTransport1».

Hide image

Вот и всё! Наш сервер готов, и нам не нужно реализовывать ничего особого на стороне сервера, т.к. механизм обратных вызовов встроен в компонент «DSServer1». У нас также есть компонент для транспорта, так что внешние клиенты могут взаимодействовать с экземпляром «DSServer1».

«Save All», «Run without Debugging» и минимизируйте серверное приложение. Оно должно быть запущено до окончания данного примера.

    Создание клиентского приложения

Теперь настало время реализовать клиентское приложение. Просто кликните на проектную группу в менеджере проектов и выберите «Add New Project».

 Hide image

В диалоге «New Items» выберите «VCL Forms Application» из категории «Delphi Projects».

Кликните на «ОК». Новый проект автоматически добавится в проектную группу.

Кликните на «File -> Save All».

Выберите папку, куда был сохранен проект, и сохраните модуль главной формы клиента как «FormClientUnit», а новый проект как «SimpleCallbacksClient» и всю проектную группу как «SimpleCallbacksGrp».

    Реализация обратного вызова

Следующим шагом является создание нового класса для обратного вызова, производного от «TDBXCallback», и реализации его метода «Execute». Этот метод будет вызываться асинхронно со стороны сервера для посылки уведомления клиенту.

Добавьте модуль «DBXJSON» в раздел «uses» модуля «FormClientUnit», так как именно здесь и будет находиться класс «TDBXCallback».

Создайте класс «TMyCallback» и переопределите его виртуальный абстрактный метода «Execute». Существуют два варианта для переопределении метода «Execute». Один принимает и возвращает «TObject», а второй принимает и возвращает «TJSONValue». Я собираюсь использовать второй вариант, т.к. оба метода для передачи значения используют формат представления JSON.

На этой стадии исходный код клиента выглядит так:

unit FormClientUnit;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, DBXJSON;

type
  TMyCallback = class(TDBXCallback)
  public
    function Execute(const Arg: TJSONValue): TJSONValue; override;
  end;

  TFormClient = class(TForm)
  private
    { Private declarations }
  public
    { Public declarations }
  end;

var
  FormClient: TFormClient;

implementation

{$R *.dfm}

{ TMyCallback }

function TMyCallback.Execute(const Arg: TJSONValue): TJSONValue;
begin
  // ...
end;

end.

А что же происходит, когда вызывается метод «Execute»? Это реально зависит от программиста и логики приложения. Чтобы не усложнять пример, мы добавим компонент «memo» на клиентскую форму, а когда вызывается метод обратного вызова «Execute», мы будем добавлять строчку текста в «memo» с содержимым параметра «Arg», преобразованного в строку.

Давайте зададим открытый метод для класса формы и назовем его «LogMsg», который получит строковый параметр с сообщением для отображения в «memo». Также мы добавим «штамп времени».

Поместим компонент «TMemo» на клиентскую форму. В инспекторе объектов измените его название на «MemoLog».

Добавьте к классу «TFormClient» открытую процедуру «LogMsg(const s: string)» и реализуйте её следующим способом:

procedure TFormClient.LogMsg(const s: string);
begin
  MemoLog.Lines.Add(DateTimeToStr(Now) + ': ' + s);
end;

А вот теперь более интересный трюк. Нам нужно обеспечить потоково-безопасный вызов процедуры «TFormClient.LogMsg» из нашего метода «TMyCallback.Execute».

Давайте создадим потоково-безопасную версию нашего метода «LogMsg», которая будет вызываться в другом потоке.

procedure TFormClient.QueueLogMsg(const s: string);
begin
  TThread.Queue(nil,
    procedure
    begin
      LogMsg(s)
    end
  );
end;

Синтаксис использованного анонимного метода может показаться экзотичным на первый взгляд, но пусть это будет куском кода, который передается как переменная. Мы просто передаем блок кода как второй параметр в метод «TThread.Queue». Этот метод является «классовым» методом класса «TThread», так что нам не нужно инстанцировать (создавать) объект «TThread» для того чтобы вызвать его метод.

Теперь мы можем вызвать потоково-безопасную версию метода «LogMsg» непосредственно из метода «TMyCallback.Execute».

function TMyCallback.Execute(const Arg: TJSONValue): TJSONValue;
begin
  FormClient.QueueLogMsg(Arg.ToString);
  Result := TJSONTrue.Create;
end;

Мы можем возвращать любые данные из метода «Execute», а поскольку эти данные могут быть любыми, мы просто вернем JSON-значение «true».

Теперь нам нужно зарегистрировать обратный вызов на сервере, так чтобы он знал, что вызывать «в обратную сторону».

Для управления обратными вызовами существует специальный класс «TDSClientCallbackChannelManager». Он объявлен в модуле «DSHTTPCommon».

Поместите компонент «TDSClientCallbackChannelManager» на форму и задайте его свойства в инспекторе объектов.

Нам нужно определить имя канала на сервере, с которым будет ассоциирован конкретный обратный вызов. Зададим его как «DelphiLabsChannel».

Также нам нужно задать свойства «CommunicationProtocol», «DSHostname» и «DSPort».

Hide image

Следующее, что мы должны сделать, это очистить свойство «ManagerId», т.к. мы собираемся сгенерировать это значение в процессе работы (runtime).

Это – очень важный момент. Нам нужно, чтобы каждый экземпляр клиентского приложения был по-разному идентифицирован на сервере. Значение «ManagerID» используется на сервере для идентификации клиентов, так что это значение должно отличаться для каждого экземпляра клиента.

Мы собираемся использовать метода «TDSClientCallbackChannelManager.RegisterCallback» для регистрации экземпляра обратного вызова на сервере. Этот метод принимает два параметра: название обратного вызова, который уникально идентифицирует его на сервере, и ссылку на экземпляр обратного вызова. В нашем случае это «FMyCallback».

Если посмотреть в конструктор класса «TDSClientCallbackChannelManager», то можно увидеть, что значение «ManagerId» генерируется на основе вызова метода «TDSTunnelSession.GenerateSessionId», который возвращает случайную строку из трех чисел. Мы будем использовать эту функциональность для генерации случайных имен наших экземпляров обратного вызова.

Добавьте закрытое поле «FCallbackName: string» к классу формы и добавьте код для инициализации его в событии «OnCreate». Нам потребуется добавить модуль «DSService» в раздел «uses», т.к. именно в нем определен класс «TDSTunnelSession».

Нам также нужно добавить код для инициализации свойства «DSClientCallbackChannelManager1.ManagerId».

uses DSService; // for “TDSTunnelSession”

// … 

procedure TFormClient.FormCreate(Sender: TObject);
begin
  DSClientCallbackChannelManager1.ManagerId := 
    TDSTunnelSession.GenerateSessionId;

  FMyCallbackName := 
    TDSTunnelSession.GenerateSessionId;

  DSClientCallbackChannelManager1.RegisterCallback(
    FMyCallbackName,
    TMyCallback.Create
  );
end;

«DSClientCallbackChannelManager1» владеет ссылкой на обратный вызов, которую мы передаем в метод «RegisterCallback», поэтому нам не нужно как-то по-особому хранить ее.

Вот теперь мы готовы принимать обратные вызовы. На следующем шаге мы реализуем функциональность «вещания в канал». Все вызовы, зарегистрированные в рамках определенного канала, будут реализовывать уведомления с сервера.

    Вещание в канал

Обратите внимание, что мы могли бы создать полностью другое клиентское приложение, чтобы вещать в канал, а созданное нами приложение, как оно сейчас реализовано, лишь бы принимало уведомления.

Но для простоты мы просто добавим функциональность вещания в канал к нашему демонстрационному приложению, а затем запустим два экземпляра того же самого клиентского приложения, которые будут обмениваться сообщениями.

Первое, что нужно сделать, это добавить компонент «TSQLConnection» на форму для связи с сервером. Возможно, самый простой путь для этого – воспользоваться сервисом «IDE Insight». Просто нажмите F6 и начните вводить «TSQLConnection» для поиска, а затем для добавления его на форму.

Задайте свойство «Driver» компонента «SQLConnection1» как «DataSnap».

Задайте свойство «LoginPrompt» как «False».

Установите свойство «Connected» в «True» для проверки, сможет ли клиент соединиться с сервером.

Согласно типовому сценарию на данной стадии нам нужно сгенерировать код клиентвкого прокси DataSnap, чтобы появилась возможность вызывать серверные методы. Однако в данном случае, этот шаг не является необходимым, т.к. у нас нет пользовательских методов на сервере! Генератор клиентского прокси DataSnap использует класс «TDSAdminClient» в качестве базового для классов клиентских прокси. Этот класс уже содержит значительную часть функциональности, которая готова к использованию, включая вещание в канал и уведомления посредством обратных вызовов. Мы будем использовать класс «TDSAdminClient» напрямую как средство взаимодействия с сервером.

Нам нужно немного расширить интерфейс пользователя клиентского приложения для поддержки вещания в канал.

Добавьте компонент «TButton» на форму. Установите его свойство «Name» в «ButtonBroadcast», а свойство «Caption» в «Broadcast to Channel».

Добавьте компонент «TEdit». Установите его свойство «Name» в «EditMsg» и введите в него произвольное сообщение.

Также можно добавить метку к полю ввода сообщения, чтобы обозначить место для ввода сообщений.

Кликните два раза на кнопке и добавьте следующий код для посылки сообщений методом вещания в канал. Заметьте, что мы можем посылать любые сложные данные в JSON-формате, соответственно, сообщением может быть и сложнее, чем просто строка.

uses DSProxy; // <- for “TDSAdminClient”

// …

procedure TFormClient.ButtonBroadcastClick(Sender: TObject);
var AClient: TDSAdminClient;
begin
  AClient := TDSAdminClient.Create(SQLConnection1.DBXConnection);
  try
    AClient.BroadcastToChannel(
      DSClientCallbackChannelManager1.ChannelName,
      TJSONString.Create(EditMsg.Text)
    );
  finally
    AClient.Free;
  end;
end;

Обратите внимание, что если запустить клиентское приложение и нажать на кнопку «вещать», то сообщение, введенное в поле вода, будет получено функцией обратного вызова и отобразится в «мемо».

Запустите второй экземпляр клиентского приложения, и вы увидите, что сообщения, посланные от одного приложения, будут получать все клиенты!

Hide image

Это – нереально красиво! Мы теперь можем транслировать любые данные, которые закодированы с помощью JSON, различным приложениям, запущенным в сети.

А как насчет чистого взаимодействия по типу «точка-точка»? Может быть, нам не нужно транслировать все сообщения в канал?

Будет гораздо лучше, если определенный клиент посылает сообщение другому за счет обращения только к одному экземпляру клиентского callback-а.

Это также возможно, но нам придется расширить наше клиентское приложение для поддержки вызовов только заданных callback-ов.

    Запуск обратных вызовов уведомлением

Остановите обоих клиентов, но оставьте сервер запущенным.

Класс «TDSAdminClient» также содержит метод «NotifyCallback», который может быть использован для реализации модели «точка-точка». У этого метода следующая сигнатура:

function TDSAdminClient.NotifyCallback(ChannelName: string; ClientId: string; CallbackId: string; Msg: TJSONValue; out Response: TJSONValue): Boolean;

Параметр «ChannelName» задает имя коммуникационного канала, с которым ассоциирован экземпляр обратного вызова целевого клиента. «ClientID» и «CallbackID» представляют собой значения, которые передаются в метод «RegisterCallback» компонента «DSClientCallbackChannelManager1» в целевом клиенте. Они оба генерируются случайным образом. «Msg» представляет собой JSON-значение, которое содержит информацию, которую мы хотим послать в целевой callback, а «Response» является возвращаемым параметром (out-параметром) и содержит JSON-значение с закодированным ответом.

Существует также «TDSAdminClient.NotifyObject», который принимает схожие параметры, но вместо использования значения «TJSONValue» для ввода и вывода параметров, он использует потомки TObject, которые автоматически сериализуются и десериализуются из представления в виде JSON.

Процесс вызова индивидуального callback-а уведомлением подразумевает немного ручного труда, так как придется копировать и вставлять значения «ClientID» и «CallbackID» из одного запущенного экземпляра в другой.

Давайте добавим к форме нашего клиентского приложения четыре компонента «TEdit», четыре «TLabel» и четыре «TButton».

Измените свойство «Caption» кнопки на «Notify Callback» и переименуйте поля ввода на: «EditLocalClientId», «EditLocalCallbackId», «EditDestinationClientId», «EditDestinationCallbackId».

В событие «OnCreate» клиентской формы добавьте код, инициализирующий поля ввода:

EditLocalClientId.Text := DSClientCallbackChannelManager1.ManagerId;
EditLocalCallbackId.Text := FMyCallbackName;
EditDestinationClientId.Text := '';
EditDestinationCallbackId.Text := '';

Дважды кликните на кнопку «Notify Callback» и введите следующий код для уведомления удаленного callback-а:

procedure TFormClient.ButtonNotifyClick(Sender: TObject);
var AClient: TDSAdminClient; aResponse: TJSONValue;
begin
  AClient := TDSAdminClient.Create(SQLConnection1.DBXConnection);
  try
    AClient.NotifyCallback(
      DSClientCallbackChannelManager1.ChannelName,
      EditDestinationClientId.Text,
      EditDestinationCallbackId.Text,
      TJSONString.Create(EditMsg.Text),
      aResponse
    );
  finally
    AClient.Free;
  end;
end;

Теперь запустите два или более клиентских приложений и скопируйте «ClientId» и «CallbackId» из клиента, в какой вы хотите послать уведомление, и вставьте в поля «destination» клиента, который должен послать уведомление.

Hide image

Вот оно и работает! Мы только что осуществили коммуникацию «точка-точка» между клиентскими приложениями DataSnap!

    Расширяя горизонты

Основной идеей обучающего примера было показать всё максимально просто.

Также возможно использовать обратный вызовы через протокол HTTP в дополнение к TCP/IP. Мы использовали в данном примере архитектуру DataSnap DBX, однако обратные вызовы осуществимы и для DataSnap REST.

RAD Studio XE предлагает очень интересный демонстрационный проект, который показывает все эти возможности.

Вы можете загрузить этот проект непосредственно из IDE с использованием Subversion.

Выберите «File -> Open from Version Control…», а затем введите следующий URL в поле «URL or Repository…»:

https://radstudiodemos.svn.sourceforge.net/svnroot/radstudiodemos/branches/RadStudio_XE/Delphi/DataSnap/CallbackChannels

В поле «Destination» введите папку, в которую вы хотите загрузить демо-проект. В моем случае я создал локальную папку «C:\DataSnapLabs\CallbackChannelsDemo».

Hide image

Просто нажмите ОК и наберитесь терпения. Сначала вы увидите пустое окно с заголовком «Updating…». После неутомительного ожидания оно отобразит называние всех файлов, которые сгружаются с публичного репозитория на SourceForge.

Hide image

Здесь три проекта. «ChannelsServerProject» является главным серверным приложением. «DBXClientChannels» и «RESTClientChannels» представляют собой два клиентских приложения. Одно базируется на архитектуре DataSnap DBX, а другое – на новой архитектуре DataSnap REST, появившейся в RAD Studio XE.

Выберите серверный проект и нажмите на кнопку ОК для открытия его в IDE.

Hide image

Кликните на «ОК» для закрытия окна «Updating». На этой стадии только серверный проект является открытым в IDE.

Теперь нам нужно добавить оба клиентских проекта к проектной группе так, чтобы все три демонстрационных проекта были доступны в IDE.

Кликните правой кнопкой мыши на узле «Project Group» в окне «Project Manager», выберите «Add Existing Project», и выберите проект «DBXClientChannels».

Кликните правой кнопкой снова на «Project Group», выберите «Add Existing Project» и в этот раз выберите проект «RESTClientChannels».

Выберите «File -> Save All» или просто кликните на иконке «Save All».

Дайте проектной группе название. Я назвал её «CallbackChannelsDemo».

На этой стадии мой «Project Manager» выглядит так:

Hide image

А дальше я предлагаю вам самим проработать с данным примером и в удобном для вас темпе ознакомиться с возможностями DataSnap XE.

    Итог

В этом учебном примере мы использовали Delphi XE для создания системы, состоящей из сервера и клиента как приложений Win32, которые взаимодействуют друг с другом посредством протокола TCP/IP и используют обратные вызовы.

Механизм обратных вызовов представляет собой очень полезную альтернативу традиционной системе обмена сообщениями по типу «запрос/ответ» в распределенных приложениях.

С использованием callback-ов серверное приложение может посылать асинхронные уведомления одному или многим экземплярам классов обратных вызовов, находящихся внутри клиентских приложений.

Полный исходный код для данной статьи доступен на Embarcadero CodeCentral

http://cc.embarcadero.com/item/28288

Видео-версия описанных шагов в данной статье доступна на YouTube. Она состоит из трех частей:

Более подробную информацию о Delphi можно найти на соответствующей странице:

http://www.embarcadero.com/products/delphi


Учебный пример: Простой сервис DataSnap...

Written by Vsevolod L1433 on . Posted in ENTERPRISE

    Создаем простое серверное приложение

Первым шагом является создание нового серверного приложения DataSnap с использованием мастера “DataSnap Server”.

Запустите Delphi XE.

Выберите в главном меню “File -> New -> Other”, а затем в диалоге “New Items” щелкните два раза на иконке “DataSnap Server” в категории “Delphi Projects -> DataSnap Server”.

Hide image

Hide image
Click to see full-sized image

На первой странице оставьте по-умолчанию опцию “Project type” как “VCL Forms Application”.

Hide image
Click to see full-sized image

На второй странице мастера также оставьте уже выбранные опции. Далее мы заменим демонстрационные методы (Sample Methods) сервера нашими вариантами.

Hide image
Click to see full-sized image

На третьем изображении мы оставим значение по-умолчанию “211” для номера порта TCP/IP. Старайтесь всегда проводить проверку доступности порта путем нажатия на кнопку “Test Port”.

Hide image
Click to see full-sized image

На последней странице мы собираемся также воспользоваться опцией по-умолчанию для выбора базового класса как “TComponent”, от которого будет производиться наш класс, реализующий методы сервера.

Нажмите на кнопку “Finish”, а мастер создаст новый проект с тремя модулями.

Сохраните весь проект выбором “File -> Save All”.

Создайте новую папку для всех файлов для данного учебного примера “C:\DataSnapLabs\SimpleCalc\”.

Сохраните главную форму приложения как “FormServerUnit”, а для остальных имен воспользуйтесь стандартными названиями – “ServerContainerUnit1” и “ServerMethodsUnit1” – и сохраните проект как “SimpleCalcServer”.

На этой стадии в окне Delphi Project Manager вы видите следующую картину:

Hide image

Откройте модуль “ServerMethodsUnit1.pas” и реализуйте функциональность простого сервиса, выполняющего арифметические действия. Замените демонстрационные методы, добавленные мастером (“EchoString” и “ReverseString”), на нужные нам варианты “Add” («сложить»), “Subtract” («вычесть»), “Multiply” («умножить») и “Divide” («разделить»).

Исходный код для модуля “ServerMethodsUnit1” должен выглядеть следующим образом:

unit ServerMethodsUnit1;

interface

uses
  Classes;

type
{$METHODINFO ON}
  TServerMethods1 = class(TComponent)
  private
    { Private declarations }
  public
    function Add(a, b: double): double;
    function Subtract(a, b: double): double;
    function Multiply(a, b: double): double;
    function Divide(a, b: double): double;
  end;
{$METHODINFO OFF}

implementation

{ TServerMethods1 }

function TServerMethods1.Add(a, b: double): double;
begin
  Result := a + b;
end;

function TServerMethods1.Subtract(a, b: double): double;
begin
  Result := a - b;
end;

function TServerMethods1.Multiply(a, b: double): double;
begin
  Result := a * b;
end;

function TServerMethods1.Divide(a, b: double): double;
begin
  Result := a / b;
end;

end.

Листинг 1: ServerMethodsUnit1.pas.

Вы полностью реализовали наш сервер. Для того чтобы реализовать клиентское приложение, сервер должен быть запущен.

Выберите “Run -> Run Without Debugging” для запуска сервера (вне отладчика) и минимизируйте его окно.

Не завершайте работу серверного приложения до окончания учебного примера.

    Создаем простое клиентское приложение

Кликните правой кнопкой мыши на project group внутри Project Manager и выберите “Add New Project”.

Hide image

В диалоге “New Items” выберите “VCL Forms Application” из категории “Delphi Projects”.

Hide image

Нажмите “OK”. К существующей project group должен добавиться новый проект.

Выполните сохранение нового проекта “File -> Save All”.

Выберите папку, которую вы создали для сохранения файлов проекта сервера, и сохраните туда главную форму клиентского приложения как “FormClientUnit”, сам проект как “SimpleCalcClient”, а всю project group как “SimpleCalcGrp”.

Теперь окно Project Manager должно выглядеть как:

Hide image

Проверьте, что проект клиентского приложения активен, затем выберите “File -> New -> Other”, а в появившемся диалоге “New Items” выберите “DataSnap Client Module”.

Hide image

Как и в прошлый раз, пожалуйста, сохраните все опции по умолчанию во время работы с мастером.

Hide image
Click to see full-sized image

На первой странице оставьте “DataSnap server location” как “Local server”.

Hide image
Click to see full-sized image

Наш сервер DataSnap является “stand alone” (независимое отдельное приложение), поэтому просто нажмите “Next”.

Hide image
Click to see full-sized image

Мы согласились использовать “TCP/IP” в качестве протокола, поэтому сохраним выбранную опцию.

Hide image
Click to see full-sized image

Нажмите на “Test Connection”, чтобы проверить, действительно ли сервер «слушает» порт 211, а затем нажмем “Finish”.

Мастер добавить два модуля к нашему клиентскому приложению “ClientClassesUnit1” и “ClientModuleUnit1”.

Последней задачей в этом учебном примере является реализация интерфейса клиентского приложения.

Начнем с того, что добавим ссылку на модуль “ClientModuleUnit1” в раздел uses главного модуля формы клиента. Активируем данный модуль в редакторе и выбираем в меню “File -> Use Unit”.

Теперь в коде формы клиента нужно правильно использовать методы, экспонируемые (предлагаемые) через свойство “ClientModule1.ServerMethods1Client”. Эти методы имеют те же имена и сигнатуры, как и методы, реализованные на сервере.

Ниже показана реализация главной формы клиентского приложения.

unit FormClientUnit;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls;

type
  TForm3 = class(TForm)
    EditA: TEdit;
    ButtonAdd: TButton;
    EditB: TEdit;
    ButtonSub: TButton;
    ButtonMult: TButton;
    ButtonDiv: TButton;
    LabelResult: TLabel;
    procedure ButtonAddClick(Sender: TObject);
    procedure ButtonSubClick(Sender: TObject);
    procedure ButtonMultClick(Sender: TObject);
    procedure ButtonDivClick(Sender: TObject);
  private
    { Private declarations }
  public
    function GetA: double;
    function GetB: double;
    procedure ShowResult(aValue: double);
  end;

var
  Form3: TForm3;

implementation

uses ClientModuleUnit1;

{$R *.dfm}

function TForm3.GetA: double;
begin
  Result := StrToFloat(EditA.Text);
end;

function TForm3.GetB: double;
begin
  Result := StrToFloat(EditB.Text)
end;

procedure TForm3.ShowResult(aValue: double);
begin
  LabelResult.Caption := FloatToStr(aValue);
end;

procedure TForm3.ButtonDivClick(Sender: TObject);
begin
  ShowResult(ClientModule1.ServerMethods1Client.Divide(GetA, GetB));
end;

procedure TForm3.ButtonMultClick(Sender: TObject);
begin
  ShowResult(ClientModule1.ServerMethods1Client.Multiply(GetA, GetB));
end;

procedure TForm3.ButtonSubClick(Sender: TObject);
begin
  ShowResult(ClientModule1.ServerMethods1Client.Subtract(GetA, GetB));
end;

procedure TForm3.ButtonAddClick(Sender: TObject);
begin
  ShowResult(ClientModule1.ServerMethods1Client.Add(GetA, GetB));
end;

end.

Листинг 2: Реализация главной формы клиента.

Окончательно моя форма выглядит следующим образом:

Hide image

Наверное, вам не нужно рассказывать, как воспроизвести ее!

Теперь вы видите, как просто создавать сервера и клиенты DataSnap и использованием Delphi XEJ

    Итог

В этом учебном примере мы использовали Delphi XE для построения простого сервиса DataSnap XE в виде калькулятора, который на уровне реализации представляет собой приложения Win32, при обмене данными между которыми используется протокол TCP/IP.

Весь исходный код данного учебного примера доступен по адресу http://cc.embarcadero.com/Item/28184

Видеоролик, демонстрирующий реализацию этого примера, можно найти здесь (http://www.youtube.com/watch?v=Qm__RdmIWSg).

Подробное описание возможностей технологии Delphi находится по адресу http://www.embarcadero.com/products/delphi


Delphi Labs: DataSnap XE - Callbacks

Written by Paweł Głowacki on . Posted in ENTERPRISE

    Introduction

The objective of this tutorial is to create the simplest possible DataSnap Delphi client and server applications that use callbacks for communication.

In this lab exercise, we are going to use Delphi XE to build a simple callbacks demo system consisting of server and client applications. The server application will serve as a communication hub for multiple client applications running in the network. It is a more realistic scenario as compared to sending notifications directly from server application user interface to clients. In most scenarios, a server application will not have any user interface, so callbacks are a great mechanism for clients to communicate with each other.

    Message Exchange Patterns

The most common message exchange pattern in client/server applications is “request-response”. One application (“a client”) is sending a message (“a request) to another application running in the network (“a server”) and the server sends back a message (“a response").

Hide image
Click to see full-sized image

In many real world applications, it would also be useful to have the opposite situation, where it is a server application that sends a message (“a notification”) to a client application. A server application may want to inform a client that something interesting has happened on the server. This is called “a callback” – a situation when server “calls back” the client.

expand view>>

Hide image
Click to see full-sized image

Imagine a chat application where multiple client applications connected to the server can communicate with each other. One client sends a message to the server and then the server forwards this message to one or more connected client applications.

expand view>>

Hide image
Click to see full-sized image

The possibility for the server to asynchronously send a notification to one or more clients is very useful in many scenarios.

expand view>>

    DataSnap Callbacks and Channels

In order to use callbacks in DataSnap applications, you need to define a custom callback class that is inherited from the abstract “TDBXCallback” class and override one of its virtual, abstract “Execute” methods which are called by the server and executed on the client. The “TDBXCallback” class is defined in the “DBXJSON” unit as follows (some members striped out for readability):

unit DBXJSON;

interface

// …

type
  TDBXCallback = class abstract
  public
    function Execute(const Arg: TJSONValue): TJSONValue; overload; virtual; abstract;
    function Execute(Arg: TObject): TObject; overload; virtual; abstract;
    // …
  end;

In the previous version of DataSnap that came with RAD Studio 2010 it was only possible to use so-called “lightweight” callbacks. A callback instance was passed to a long running server method as a parameter from a client, so the server could call its “Execute” method within the duration of a method call – for example to notify the client about the progress of a long running operation.

In RAD Studio XE, the latest version available, so called “heavyweight” callbacks have been introduced. They can be used throughout the whole lifetime of a client application and not only during a server method call. This opens a lot of new possibilities for building different types of applications. In the remaining part of this tutorial we are going to focus on “heavyweight” callbacks and for simplicity we are going to refer to them as just “callbacks”.

In DataSnap architecture callbacks are associated with “channels”. In general there could be multiple client applications connected to the server and each of these clients can contain zero or more callbacks. The server can “broadcast” to the channel, so all callbacks on every client that are registered with a specific channel are going to receive this notification. It is also possible to notify a specific callback using its unique identifier used during registering the callback with the server. In this way it is possible to achieve peer-to-peer communication model.

We are going to try both approaches: broadcasting to a channel and notifying a specific callback.

The server application calls “Execute” method on the client callback asynchronously. This is a very important point to realize. Every Delphi VCL Forms application has its main thread of execution and in case of multithreaded applications any calls from other threads that manipulates graphical user interface of the applications need to be synchronized. This is exactly the situation with using callbacks. The callback “Execute” method is called on a different thread then the main thread of the VCL application. There are different ways of synchronizing calls, but probably the easiest option is to use “TThread.Queue” class method, which asynchronously executes a block of code within the main thread.

    Implement the Callback Server

Our server application is going to be super simple. The callback functionality is built into the “DSServer” component, which is the central point of every DataSnap server application. In this demo we do not even need to create any server methods, because we are only going to communicate between client applications using callbacks.

The first step is to create a new DataSnap server application using “DataSnap Server” wizard.

Select “File -> New -> Other” and from the “New Items” dialog double-click on the “DataSnap Server” icon in the “Delphi Projects -> DataSnap Server” category.

Hide image
Click to see full-sized image

In the first tab keep the default DataSnap “Project type” which is “VCL Forms Application”.

expand view>>

Hide image
Click to see full-sized image

On the second tab we keep “TCP/IP” as the communication protocol and we can uncheck the option for generating “server methods class”, because it is not needed for this simple callbacks demo. If you leave the default option to generate server methods, it is not a problem. We are just not going to use them.

expand view>>

Hide image
Click to see full-sized image

On the third screen we keep the default value “211” for the TCP/IP Port. It is always a good idea to click on the “Test Port” to make sure that it is available.

expand view>>

Because we have unchecked the option to generate a server class earlier in the wizard, we are not presented with the screen to select a base class for our server method class.

Click on “Finish” and the wizard should create a new project with just two units: main form and server container. There is no server methods unit this time.

Click on “File -> Save All”.

Create a new directory for all files in this lab – for example “C:\DataSnapLabs\SimpleCallbacks”.

Save main application form as “FormServerUnit” and keep the default name for the server container unit – typically “ServerContainerUnit1”.

Save project as “SimpleCallbacksServer”.

Select the main form in the Object Inspector and change its “Name” property to “FormServer” and its “Caption” property to “Delphi Labs: DataSnap XE - Simple Callbacks – Server”.

Hide image
ServerForm

Open the server container unit and verify that there are only two components there: “DSServer1” and “DSTCPServerTransport1” components.

Hide image
ServerContainer

That’s it. Our server is ready and we do not need to implement anything special on the server, because the support for callbacks is built into the “DSServer1” component. We also have a transport component so that external clients can communicate with the “DSServer1” instance in the server.

“Save All”, “Run without Debugging” and minimize the server application. It should be running for the rest of this tutorial.

    Create the Client Application

Now it is the time to create a client. Just right click on the “Project Group” node in the “Project Manager” window and select “Add New Project”.

Hide image

From the “New Items” dialog select “VCL Forms Application” from “Delphi Projects” category.

Hide image

Click “OK”. A new project will be added to the existing project group.

Click on “File -> Save All”.

Locate the folder where the server project has been saved and save there the main form unit of the client application as “FormClientUnit”, the new project as “SimpleCallbacksClient” and the project group as “SimpleCallbacksGrp”.

    Implementing a callback

The next step is to define a new callback class derived from “TDBXCallback” and implement its “Execute” method. This method will be called asynchronously by the server to notify the client.

Add “DBXJSON” unit to the “uses” clause of “FormClientUnit”, because this is where “TDBXCallback” class is defined.

Define “TMyCallback” class and override its virtual abstract “Execute” method. There are two variants of the “Execute” method you could override. One that takes and returns a “TObject” and the second that takes and returns “TJSONValue”. I’m going to use the second option, because at the end both methods use JSON as the underlying format for sending messages.

At this stage the client unit source code looks like this:

unit FormClientUnit;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, DBXJSON;

type
  TMyCallback = class(TDBXCallback)
  public
    function Execute(const Arg: TJSONValue): TJSONValue; override;
  end;

  TFormClient = class(TForm)
  private
    { Private declarations }
  public
    { Public declarations }
  end;

var
  FormClient: TFormClient;

implementation

{$R *.dfm}

{ TMyCallback }

function TMyCallback.Execute(const Arg: TJSONValue): TJSONValue;
begin
  // ...
end;

end.

So what should happen when our callback’s “Execute” method is called? This is really up to the programmer and depends on the application logic. To keep this example simple, we are going to add a memo component to the client form and when the callback “Execute” method is called we are going to add a text line to the memo with the contents of the “Arg” parameter converted to a text string.

Let’s define a public method on the form class called “LogMsg” that will take a string parameter with a message to display in the memo. We are also going to add a timestamp.

Drop “TMemo” component on the client form. Change its name in the Object Inspector to “MemoLog”.

Add to “TFormClient” class a public procedure “LogMsg(const s: string)” and implement it in the following way:

procedure TFormClient.LogMsg(const s: string);
begin
  MemoLog.Lines.Add(DateTimeToStr(Now) + ': ' + s);
end;

Now is the tricky part. We need to make a thread-safe call to the “TFormClient.LogMsg” procedure from our “TMyCallback.Execute” method.

Let’s define the thread-safe version of our “LogMsg” method, so it could be called from a different thread.

procedure TFormClient.QueueLogMsg(const s: string);
begin
  TThread.Queue(nil,
    procedure
    begin
      LogMsg(s)
    end
  );
end;

The syntax for using anonymous methods may seem to be exotic at first, but think about it like treating code as data. You just pass a block code as the second parameter to “TThread.Queue” method. This method is a “class” method of the “TThread” class, so we do not need to instantiate “TThread” object in order to be able to call it.

Now we can call the thread-safe version of our “LogMsg” method directly from the “TMyCallback.Execute” method.

function TMyCallback.Execute(const Arg: TJSONValue): TJSONValue;
begin
  FormClient.QueueLogMsg(Arg.ToString);
  Result := TJSONTrue.Create;
end;

We can return anything from our “Execute” method, as long as we do return something, so we just return JSON “true” value.

Now we need to register our callback with the server, so it is informed about what to “call back”.

There is a special class designed for managing client callbacks called “TDSClientCallbackChannelManager” and it is defined in the “DSHTTPCommon” unit.

Drop a “TDSClientCallbackChannelManager” component on the form and set its properties in the Object Inspector.

We need to select a name for a channel on the server that we want to associate our callback with. Let’s call our channel “DelphiLabsChannel”.

We also need to specify “CommunicationProtocol”, “DSHostname” and “DSPort” properties.

Hide image
CallbackChannelMgrProperties

The next thing we are going to do is to clear the “ManagerId” property, because we are going to generate this value at runtime.

This is a very important thing to do. We want every client application instance to be treated by the server differently. The “ManagerId” value is used at the server to identify clients, so this value has to be different for every client instance.

We are going to use “TDSClientCallbackChannelManager.RegisterCallback” method to register our callback instance with the server. This method takes two parameters: the name of the callback the uniquely identifies it on the server and the reference to the callback instance, in our case this will be “FMyCallback”.

If you look into the constructor of the “TDSClientCallbackChannelManager” class you will see that the value for “ManagerId” is generated by a call to “TDSTunnelSession.GenerateSessionId” method that returns a random string made of three numbers. We are going to use this functionality to generate a unique name for our callback instance.

Add “FCallbackName: string” private field to the form class and add code to initialize it in the form’s “OnCreate” event. You will also need to add “DSService” unit to the “uses” clause, because this is where the “TDSTunnelSession” class is defined.

We also need to add code to initialize “DSClientCallbackChannelManager1.ManagerId” property.

uses DSService; // for “TDSTunnelSession”

// … 

procedure TFormClient.FormCreate(Sender: TObject);
begin
  DSClientCallbackChannelManager1.ManagerId := 
    TDSTunnelSession.GenerateSessionId;

  FMyCallbackName := 
    TDSTunnelSession.GenerateSessionId;

  DSClientCallbackChannelManager1.RegisterCallback(
    FMyCallbackName,
    TMyCallback.Create
  );
end;

The callback reference that we pass to the “RegisterCallback” method is owned by the “DSClientCallbackChannelManager1”, so we do not need to keep this reference.

At this point we are ready to receive callbacks. The next step is to implement a functionality to broadcast to a channel. All callbacks registered with a specified channel are going to be notified by the server.

    Broadcasting to the Channel

Note that we could now create a completely different client application for broadcasting to the channel, and our client application, as it is implemented now, would be able to receive the notifications.

To keep things simple, we are going to add broadcasting to channel functionality to our demo client application, and later we are going to run two instances of the same client application to see if we can send messages from one client to another.

The first thing to do on the client is to add a “TSQLConnection” component to the form in order to be able to connect to the server. Probably the easiest way to do it is with “IDE Insight”. Just press F6 and start typing “TSQLConnection” to search for it and then select to add to the form.

Set the “Driver” property of “SQLConnection1” component on the form to “DataSnap”.

Set “LoginPrompt” property to “False”.

Set the “Connected” property to “True” to verify that the client is able to connect to the server.

In a typical scenario, at this stage we would need to generate DataSnap client proxy code in order to be able to call server methods. In this case, however, this step is not necessary, since there are no custom server methods on the server! The Delphi DataSnap proxy generator uses “TDSAdminClient” class as a base class for client proxy classes. This class already contains quite a lot of functionality that can be used on its own, including broadcasting to channels and notifying callbacks. We are going to use “TDSAdminClient” class directly as the way to interact with the server.

We need to extend our client application user interface a bit to support broadcasting to a channel.

Add “TButton” component to the form. Set its “Name” property to “ButtonBroadcast” and its “Caption” property to “Broadcast to Channel”.

Add a “TEdit” component. Set its “Name” property to “EditMsg” and optionally enter some default message into it.

You can also add a label next to the message edit, to indicate that this is the place to enter messages.

Double-click on the button and add the following code to be able to broadcast to messages to a channel. Note that we could pass arbitrary complex data encoded as JSON, so it could be something more complex than just a string.

uses DSProxy; // <- for “TDSAdminClient”

// …

procedure TFormClient.ButtonBroadcastClick(Sender: TObject);
var AClient: TDSAdminClient;
begin
  AClient := TDSAdminClient.Create(SQLConnection1.DBXConnection);
  try
    AClient.BroadcastToChannel(
      DSClientCallbackChannelManager1.ChannelName,
      TJSONString.Create(EditMsg.Text)
    );
  finally
    AClient.Free;
  end;
end;

Now if you run the client application and press on the broadcast button you should see your message entered into the edit received by the callback and displayed in the memo.

Run the second instance of the client application and you should be able to see that messages sent from one application are received by all applications!

Hide image
Click to see full-sized image

That is cool! We can now broadcast arbitrary data that can be encoded in JSON to multiple applications running in the network.

expand view>>

What about pure peer-to-peer communication? Maybe I do not want to send message to all callbacks in the channel?

It would be much better if a given client could send message to one and only one callback instance on a different client.

This is also possible, but and we need to extend our client application to support notifying specific callbacks.

    Notifying Callbacks

Stop both clients, but keep the server running.

The “TDSAdminClient” class also contains “NotifyCallback” method that could be used to achieve peer-to-peer communication model. This method has the following signature:

function TDSAdminClient.NotifyCallback(ChannelName: string; ClientId: string; CallbackId: string; Msg: TJSONValue; out Response: TJSONValue): Boolean;

The “ChannelName” parameter specifies the name of the communication channel the destination client callback is associated with. “ClientId” and “CallbackId” are values that were passed to “RegisterCallback” method of the “DSClientCallbackChannelManager1” at the destination client instance. They were both generated randomly. “Msg” is the JSON value that contains information that we want to send to the destination callback and “Response” is an “out” parameter and contains JSON value with encoded response.

There is also “TDSAdminClient.NotifyObject” that takes similar parameters, but instead of using “TJSONValue” for input and output parameters, it is using a TObject-descendant that is automatically serialized and deserialized from its JSON representation.

The process of notifying individual callbacks is going to be a little bit manual that will involve copying and pasting “ClientId” and “CallbackId” values from one running instance to another.

Let’s add to our client application four additional “TEdit” components, four “TLabel” components and a “TButton”.

Change “Caption” property of the button to “Notify Callback” and rename edits to: “EditLocalClientId”, “EditLocalCallbackId”, “EditDestinationClientId”, “EditDestinationCallbacksId”.

In the “OnCreate” event of the client form add code to initialize edits:

  
EditLocalClientId.Text := DSClientCallbackChannelManager1.ManagerId;
EditLocalCallbackId.Text := FMyCallbackName;
EditDestinationClientId.Text := '';
EditDestinationCallbackId.Text := '';

Double-click on the “Notify Callback” button and enter the following code to notify remote callback:

procedure TFormClient.ButtonNotifyClick(Sender: TObject);
var AClient: TDSAdminClient; aResponse: TJSONValue;
begin
  AClient := TDSAdminClient.Create(SQLConnection1.DBXConnection);
  try
    AClient.NotifyCallback(
      DSClientCallbackChannelManager1.ChannelName,
      EditDestinationClientId.Text,
      EditDestinationCallbackId.Text,
      TJSONString.Create(EditMsg.Text),
      aResponse
    );
  finally
    AClient.Free;
  end;
end;

Now start two or more client application instances and copy “ClientId” and “CallbackId” from a client that you would like to receive notifications to “destination” edits of the client you want to send notification.

Hide image
Click to see full-sized image

That’s it! We have implemented peer-to-peer communication between Delphi DataSnap client applications!

expand view>>

    The Bigger Picture

The idea behind this Delphi Labs demo was to make it as simple as possible.

It is also possible to use callbacks with the HTTP protocol in addition to TCP/IP. Similarly in this demo we have used DataSnap DBX architecture, but the callbacks are also available with DataSnap REST.

RAD Studio XE comes with a very interesting demo project that demonstrates all these possibilities.

You can open this demo directly from inside the IDE using new Subversion integration.

Select “File -> Open from Version Control…” and enter the following URL in the “URL or Repository…” text box:

https://radstudiodemos.svn.sourceforge.net/svnroot/radstudiodemos/branches/RadStudio_XE/Delphi/DataSnap/CallbackChannels

In the “Destination” text box enter a folder that you want to download the demo to. In my case I have created a local “C:\DataSnapLabs\CallbackChannelsDemo” folder.

Hide image
checkout

Just click on OK and be patient. You will see initially empty window with “Updating…” title. After a while it will show the names of all files checked out from the “radstudiodemos” public repository on SourceForge.

Hide image
select project to open

There are three projects there. “ChannelsServerProject” is the main server application. “DBXClientChannels” and “RESTClientChannels” are two client applications. One based on DataSnap DBX architecture and one based on the new DataSnap REST architecture introduced in RAD Studio XE.

Keep the server project selected and click OK to open it in the IDE.

Hide image
Click to see full-sized image

Click “OK” to close the “Updating” window. At this stage only the server project is opened in the IDE.

expand view>>

Now we need to add both client projects to a project group, so we have all three demo projects available inside the IDE.

Right click on the “Project Group” node in the “Project Manager” window, select “Add Existing Project” and choose “DBXClientChannels” project.

Right click again on the “Project Group”, select “Add Existing Project” and this time choose “RESTClientChannels” project.

Select “File -> Save All” or just click on the “Save All” icon.

Give the project group a name. I have chosen for “CallbackChannelsDemo”.

At this stage my “Project Manager” looks like this:

Hide image
CallbackChannelsGrp

I will leave you here. There is plenty to explore in this demo…

    Summary

In this Delphi Lab we have used Delphi XE for building a system consisting of server and client native Win32 applications communicating with each other using TCP/IP protocol and using callbacks.

Callbacks represent a very useful alternative to a traditional request/response message exchange pattern in distributed applications.

With callbacks the server application is able to send asynchronous notifications to one or more registered callback instances inside connected client applications.

The full source code for this article is available from Embarcadero Code Central http://cc.embarcadero.com/item/28288

The video version of steps described in this article can be found on YouTube. There are three parts of the video demonstration:

More information about Delphi can be found on the Delphi home page http://www.embarcadero.com/products/delphi


Delphi Labs: DataSnap XE - WebBroker jQueryMobile ボイラープ...

Written by Chikako Yonezawa on . Posted in ENTERPRISE

    若干の免責事項

“Delphi Labs” では、Delphi での開発にフォーカスしていますが、ここで説明されているアプリケーションのすべては、RAD Studio の一部である C++Builder でも構築可能です。

Delphi と C++Builder は、とても密接に統合されています。双方は、同一の基盤である統合開発環境 (IDE: Integrated Development Environment) 上の異なる「パーソナリティ」であり、VCL (Visual Component Library) を共有しています。

Delphi Pascal プログラミング言語を使用するのは、私個人の好みです。C++ をより好む方は、C++Builder XE (http://www.embarcadero.com/jp/products/cbuilder) でお試し下さい!

    はじめに

このラボでは、jQuery Mobile JavaScript ライブラリ を使い DataSnap サーバーからデータベースのデータを読み込んで表示するための Web アプリケーションの構築を Delphi XE を使用して行います。

JavaScriptだけで行うコーディングはとても煩雑ですが、その作業を簡単に行えるようにするための多くのフレームワークとライブラリがあります。最も人気のある JavaScript ライブラリの 1つに、「迅速で簡潔」かつ「迅速な Web 開発のために、HTML ドキュメントのトラバース、イベント処理、アニメーション、Ajaxによるやり取りを単純化」する jQuery (http://jquery.com/) があります。

jQuery の世界での最も新しいプロジェクトのひとつに、iPhoneや、Android や、その他のスマートフォン上で利用可能な Web ブラウザ用に最適化された、その人気のライブラリ jQuery の “mobile” バージョンがあります。この “mobile” なルックアンドフィールをもつ Web ページを構築するための HTML5 や CSS3 のような最新の Web 標準を使用する jQuery コアの一番上のレイヤーです。

この記事を書いている時点での jQuery Mobile は、開発のアルファ段階のため、本日 (2011年3月) に使用可能な機能が、最終的なリリースと異なる可能性があります。

この記事では、http://jquerymobile.com/demos/1.0a3/ で公開されている jQuery Mobile Alpha 3 リリースを使用します。

この 2つのパートのチュートリアルの最終的な目的は、基となる InterBase XE の “Employees” サンプルデータベースからデータを供給する Delphi XE DataSnap スタンドアロンデータサーバーと、データを表示するために、jQuery Mobile を使用して WebBroker Delphi XE スタンドアロン Web アプリケーションであるクライアントから構成される、多層かつスケーラブルなサンプルの DataSnap システムを構築することです。

Hide image
Click to see full-sized image

expand view>>

    WebBroker を理解する

Delphi で構築できるアプリケーションには様々な形式があります。多くの開発者にとって、Delphi は、高速で洗練されたユーザーインターフェースを用いて高機能な Windows クライアントを構築することを意味します。Delphi で記述されたそのような強力なアプリケーションの最も良い例の一つとして、人気の通信ツール “Skype” があります。Delphi で Web アプリケーションを構築できることを知っているプログラマはそれほど多くありません。Delphi および C++Builder で Web アプリケーションを構築するための VCL フレームワークは、WebBroker と呼ばれます。

DLL (IIS のような Web サーバーへ配布することが可能) として Web アプリケーションを構築することに加えて、Delphi XE は、Web サーバーと Web アプリケーションの両方の機能を内蔵する Windows 実行可能ファイルであるスタンドアロン Web アプリケーションを、全く新しい形式として導入しました。

これは、その Web サーバーが Delphi の “Indy” コンポーネントを使用して実装されているので、非公式に “Indy WebBroker” と呼ばれています。

Delphi は、さまざまな種類の Web アプリケーションを構築するためにいくつかのウィザードを提供します。最も基本的な Web アプリケーションは、 “Web Server アプリケーション” ウィザードを使用して作成することができます。他にももっと専門的なウィザードがあります – 前 Delphi のプロダクトマネージャーである Nick Hodges の言葉 - 「スクラッチでも行えるが、より改善された出発地点を提供する」ウィザードです。例えば、伝統的な SOAP Web サービスを作成するための Delphi の “SOAP Server アプリケーション” ウィザードを使うことができますし、また、JavaScript クライアントを統合した Web ベースの DataSnap サーバーを作成するための “DataSnap REST アプリケーション” ウィザードを使うこともできます。技術的には、空の WebBroker の “Web サーバーアプリケーション” から始め、それを SOAP や REST の Web アプリケーションにするために必要なすべてのコンポーネントやコードを追加します。専用のウィザードを使うことは非常に便利です。結局のところはこれが、物事を単純化するという “ウィザード” の考え方全体なのです!

IDE のメニューより [ファイル|新規作成|その他] を選択し、「新規作成」ダイアログの [Delphi プロジェクト| WebBroker] カテゴリ内の “Web サーバーアプリケーション” アイコンをダブルクリックします。

Hide image
Click to see full-sized image

expand view>>

Web サーバーアプリケーションの形式として、 “Indy VCL アプリケーション” を選択し、[OK]ボタンをクリックします。

Hide image
DataSnap91

空の “Indy WebBroker” Web アプリケーションが作成されます。

すべてを保存します。このプロジェクトのための以下の新しいディレクトリ “C:\DataSnapLabs\WebBroker_jQueryMobileBoilerplate” を作成します。1番目のユニットには “FormMainUnit”、2番目のユニットには、“WebModuleMainUnit”、全体のプロジェクトには “WebBrokerjQueryMobileBoilerplate” と命名します。

現時点で、プロジェクトマネージャーを見ると、このようになっています。

Hide image
DataSnap92

“FormMainUnit” は、Delphi XE の新しい Indy Web サーバーの実装を内蔵するアプリケーションのメインフォームクラスを含んでいます。“WebModuleMainUnit” は、“Webモジュールクラス” の実装を含んでいます。WebBroker Web モジュールクラスは、DataSnap サーバーメソッドクラスととてもよく似たふるまいをします。それらはプログラマによってインスタンス化されず、有効期間は、フレームワークによってコントロールされます。

プロジェクトオプションのためのプロジェクトオプションダイアログを表示するため、IDE のメニューから [プロジェクト|オプション] を選択し、ダイアログ内の “フォーム” カテゴリを選択します。

Hide image
Click to see full-sized image

expand view>>

アプリケーション内で自動生成されるフォームは “FormMain” だけで、“WebModuleMain” はコード内で生成される必要があります。

Delphi プロジェクト全体のソースを見れば、この Web アプリケーションの実装の有効期間の管理がどのようになっているのかがわかります。

IDE のメニューより [プロジェクト | ソース表示] を選択すると、以下のようなコードになっているはずです。

program WebBrokerjQueryMobileBoilerplate;

{$APPTYPE GUI}

uses
  Forms,
  WebReq,
  IdHTTPWebBrokerBridge,
  FormMainUnit in 'FormMainUnit.pas' {FormMain},
  WebModuleMainUnit in 'WebModuleMainUnit.pas' {WebModuleMain: TWebModule};

{$R *.res}

begin
  if WebRequestHandler <> nil then
    WebRequestHandler.WebModuleClass := WebModuleClass;
  Application.Initialize;
  Application.CreateForm(TFormMain, FormMain);
  Application.Run;
end.

この特定のアプリケーションで使用されている Web モジュールクラスの参照を保持しているグローバルな無名メソッドの変数があります。DataSnap の場合、用意されたさまざまな有効期間のオプションを用いて、複数の多層サーバークラスを持つことも可能です。WebBroker アーキテクチャはより単純で、ただ1つの Web モジュールクラス型を持つことしかできず、それらにはデフォルトの有効期間の管理オプションが1つあります。

“Ctrl” ボタンを押したまま、エディタ内の “WebRequestHandler” 識別子上にマウスカーソルを合わせると、それが定義されているコード内の実際の場所へのハイパーリンクが表示されるようにすぐに変化させることができます。

“WebRequestHandler” グローバル変数は、“TWebModuleClass” 型のプロパティを持つ Web ハンドラクラスのインスタンスを返す無名メソッドへの参照として定義されています。この設定は、事実上にクラスファクトリのデザインパターンを実装しています。Web アプリケーションのアプリケーションロジックは、完全に Web モジュールクラスの内部に実装されます。

Web モジュールクラスは、 “TDataModule” を間接的に継承しており、http コンテンツの生成とデータアクセスのための非ビジュアル化コンポーネントのコンテナとして使用できます。すべての Web モジュールは、“TWebActionItem” コンポーネントのコレクションである published な “Actions” プロパティを持っています。

Hide image
DataSnap94

“Web サーバーアプリケーション” ウィザードは、 “DefaultHandler” と呼ばれるデフォルトの Web アクションと、その “OnAction” イベントの実装を追加してくれました。

“Actions” プロパティの省略 […] ボタンをクリックすると、簡単に Web アクションを追加できる、このプロパティ用のコレクションエディタが表示されます。

Hide image
Click to see full-sized image

expand view>>

ウィザードは、また、“Web Server Application” という文字列を表示する HTML コードのかたまりを返す “OnAction” イベント用のイベントハンドラを生成します。これは、プログラマが Web クライアントへ返却するコンテンツを実装する場所です。このイベントは “Request: TWebRequest”, “Response: TWebResponse” ,“Handled: boolean” の 3つのパラメータを持っています。“Request” パラメータは、処理される HTTP リクエストについての全ての情報を含みます。2番目のパラメータは、Web サーバーアプリケーションから、クライアントへ返却されるすべてのデータをカプセル化する “Response” オブジェクトです。“Handled” の boolean パラメータは、リクエストが完全に処理されたかを示します。より一般的なシナリオでは、1つの HTTP リクエストが、複数の WebActionItems によって処理されることが可能になります。

“Response” オブジェクトのプロパティを設定することは、プログラマの作業となります。ウィザードは、”Response.Content” プロパティへアサインするためのコードを生成しました。デフォルトのコンテンツタイプは “text/html” ですが、例えば、イメージのような他のデータ型を返却するできこともできます。そのためには、適切な MIME “ContentType” を設定する必要があります。

“Actions” コレクション内のすべての “TWebActionItem” ごとに、HTTP リクエストの HTTP メソッドの型が何か、HTTP クライアントから送られた URL からの “PathInfo”が何かを設定することができます。

“OnAction” イベントを実装する代わりに、“WebActionItem” オブジェクトのコンテンツを提供するための “Producer” コンポーネントを使うことも可能です。

何か動的コンテンツを返却するために “OnAction” イベントのデフォルトの実装を変更しましょう。

以下は、 “WebModuleMainUnit” のコンテンツ全体です。

unit WebModuleMainUnit;

interface

uses
  SysUtils, Classes, HTTPApp;

type
  TWebModuleMain = class(TWebModule)
    procedure WebModuleMainDefaultHandlerAction(Sender: TObject;
      Request: TWebRequest; Response: TWebResponse; var Handled: Boolean);
  private
    { Private declarations }
  public
    { Public declarations }
  end;

var
  WebModuleClass: TComponentClass = TWebModuleMain;

implementation

{$R *.dfm}

procedure TWebModuleMain.WebModuleMainDefaultHandlerAction(Sender: TObject;
  Request: TWebRequest; Response: TWebResponse; var Handled: Boolean);
begin
  Response.Content :=
    '<p>"Hello World" from <b>Delphi Labs</b> Delphi WebBroker test app!</p>'
  + '<p>The time at our server is <b>' + DateTimeToStr(Now) + '</b> right now.</p>'
  + '<p>HTTP response content type is: <b>' + Response.ContentType + '</b></p>';
end;

end.

ここで、Delphi WebBroker アプリケーションを実行すると、次のような出力が確認できるはずです。

Hide image
TestWebApp_output

    WebBroker jQueryMobile ボイラープレート

jQueryMobile の例をインターネットで見つけようとするなら、「ボイラープレート」と呼ばれるものをとても頻繁に見つけるでしょう。この URL: http://jquerymobile.com/demos/1.0a3/#docs/pages/page-template.html で、jQuery Mobile の実際のボイラープレート html ページのプレビューを行うことができます。これは、必要なスタイルシートとスクリプトへの参照をすべて含んだ、より洗練された jQuery Mobile Web ページを構築するための簡単なスタート地点です。

jQuery Mobile ボイラープレートページを返却するように、シンプルな Delphi WebBroker アプリケーションを変更してみましょう。

最初に、“TWebModuleMain.WebModuleMainDefaultHandlerAction” イベントハンドラを削除します。最も簡単な方法は、そのイベントの本体部分を削除して “保存” をクリックすることです。これで、イベントハンドラを完全に削除します。

Web モジュールのコードエディタとフォームデザイナ間の切り替えを行うために F12 キーを押します。Web モジュール上に、“TPageProducer” コンポーネントをドロップします。

“DefaultHandler” Web アクションアイテムを選択し、その “Producer” プロパティを設定して、Web モジュールに追加されたばかりの “PageProducer1” コンポーネントを指すようにします。

“PageProducer1” コンポーネントを選択し、オブジェクトインスペクタ内の “HTMLDoc” プロパティを開きます。

以下の jQuery Mobile ボイラープレートテンプレートをコピー&ペーストします。

<!DOCTYPE html> 
<html> 
<head> 
<title>Page Title</title> 
<link rel="stylesheet" href="http://code.jquery.com/mobile/1.0a3/jquery.mobile-1.0a3.min.css" />
<script type="text/javascript" src="http://code.jquery.com/jquery-1.4.3.min.js"></script>
<script type="text/javascript" src="http://code.jquery.com/mobile/1.0a3/jquery.mobile-1.0a3.min.js"></script>
</head> 
<body> 

<div data-role="page">

     <div data-role="header">
          <h1>Page Title</h1>
     </div><!-- /header -->

     <div data-role="content">    
          <p>Page content goes here.</p>          
     </div><!-- /content -->

     <div data-role="footer">
          <h4>Page Footer</h4>
     </div><!-- /footer -->
</div><!-- /page -->

</body>
</html>

アプリケーションを実行すると、ブラウザが開き、以下のページが表示されるはずです。

これは古いブラウザでは動きません。私は Chrome を使用しています。

Hide image
Boilerplate

HTML コードが完全にアプリケーション内にハードコードされています。 “HTMLDoc” プロパティを使う代わりに “HTMLFile” を使用することができます。最終的なアプリケーションのコンテンツが動的に生成されるので、ここで “HTMLDoc” を使用することを好みます。しかしそれほど問題にはなりません。

jQueryMobile は、ひとつの html ドキュメント内に複数の “page” を定義することができます。これを使用するつもりです。jQueryMobile は、それぞれの html タグを装飾するために “data-*” 属性を使用します。複数のモバイル “page” をホストするために、“data-role=page” 属性を持つ複数の “div” を使用する必要があります。最初にメインページを検討します。

“TPageProducer” コンポーネントは、動的な HTML コンテンツを提供するために使うことが可能な “OnHTMLTag” イベントを提供します。

“#” シンボルでマークされた特別なタグごとに PageProducer コンポーネントによって “OnHTMLTag” イベントが呼び出され、それにより、プログラムで正しい値を提供することが可能になります。

その動作を理解するために参考になるのは、“ReverseString.html” ファイルを調べることです。そして、それに対応する PageProducer は、“DataSnap REST アプリケーション” ウィザードによって生成されたプロジェクトです。

2つのカスタムタグを使用します。一つは html ドキュメントタイトル (“#doctitle”) 用、そして、もう一つは、jQueryMobile ボイラープレートテンプレートの body 全体 (“#docbody”) 用です。

“PageProducer1” コンポーネントを再び選択し、オブジェクトインスペクタ内で “HTMLDoc” プロパティを開きます。

エディタ内で、「すべて選択」し、透過的なタグを含んだ以下のコードに置き換えます。

<!DOCTYPE html> 
<html> 
<head> 
<title><#doctitle></title> 
<link rel="stylesheet" href="http://code.jquery.com/mobile/1.0a3/jquery.mobile-1.0a3.min.css" />
<script type="text/javascript" src="http://code.jquery.com/jquery-1.4.3.min.js"></script>
<script type="text/javascript" src="http://code.jquery.com/mobile/1.0a3/jquery.mobile-1.0a3.min.js"></script>
</head> 
<body> 
<#docbody>
</body>
</html>

“TPageProducer” コンポーネントを選択します。オブジェクトインスペクタ内で、このイベント用の空のイベントハンドラを生成するための “OnHTMLTag” をダブルクリックし、jQueryMobile ボイラープレートを動的に生成する以下のコードを入力します。

procedure TWebModuleMain.PageProducer1HTMLTag(Sender: TObject; Tag: TTag;
  const TagString: string; TagParams: TStrings; var ReplaceText: string);
begin
  if SameText(TagString, 'doctitle') then
    ReplaceText := 'Page Title’

  else if SameText(TagString, 'docbody') then
    ReplaceText :=

 '<div data-role="page">'

+'  <div data-role="header">'
+'       <h1>Page Title</h1>'
+'  </div><!-- /header -->'

+'  <div data-role="content">'
+'       <p>Page content goes here.</p>'
+'  </div><!-- /content -->'

+'  <div data-role="footer">'
+'       <h4>Page Footer</h4>'
+'  </div><!-- /footer -->'

+'</div><!-- /page -->'

  else
    ReplaceText := '';
end;

Web アプリケーションを再実行すると、依然として同じ jQueryMobile ボイラープレートページを表示することを確認できるはずです。

    動的ページ作成のリファクタリング

現在、すべてのマークアップは、WebBroker の透過的なタグである “#DocTitle” と “#DocBody” 用に置換マークアップを提供する “OnHTMLTag” イベント内で動的に生成されています。

今回の Web ドキュメントの実際の body を生成すめためのコードがとても複雑になりかねないので、この機能を別のデータモジュールとして抽出するのはどうでしょうか? オン・ザ・フライで、このデータモジュールを生成と破棄することができ、HTML ドキュメントの動的 body を Delphi 文字列として返却するパブリックメソッドとドキュメントタイトルを返却するためのパブリックメソッドを持つことが必要です。

IDE のメニューより [ファイル|新規作成|その他] を選択し、「新規作成」ダイアログ内の 「Delphi プロジェクト|Delphi ファイル」カテゴリを選び、「データモジュール」アイコン上でダブルクリックします。

Hide image
DataSnap96

新しいユニットを “DataModuleHtmlUnit” として保存し、オブジェクトインスペクタ内のデータモジュール名を “DataModuleHtml” に変更します。

動的ページの動的な html コンテンツの生成のプロセスの間、実行形式でこのデータモジュールクラスの生成と破棄を行います。

IDE のメニューから [プロジェクト|オプション] を選択し、このデータモジュールのインスタンス化は、今回のアプリケーションには必要ないので、プロジェクトに追加されたばかりのデータモジュールを自動生成フォームの一覧から削除します。私は、定義ユニットのグローバルな変数をコメントアウトするのが好みです。こうすれば、間違って使用されるは決してありません。

Hide image
Click to see full-sized image

expand view>>

“OnHTMLTag” イベントハンドラで使用する文字列を返却する “GetBody” と呼ばれるデータモジュールクラスのパブリックメソッドを実装しましょう。

ここでは、Web モジュールの実装は以下のようになり、“OnHTMLTag” イベントハンドラの実装を変更しています。

unit WebModuleUnit;

interface

uses
  SysUtils, Classes, HTTPApp, HTTPProd;

type
  TWebModuleMain = class(TWebModule)
    PageProducer1: TPageProducer;
    procedure PageProducer1HTMLTag(Sender: TObject; Tag: TTag;
      const TagString: string; TagParams: TStrings; var ReplaceText: string);
  private
    { Private declarations }
  public
    { Public declarations }
  end;

var
  WebModuleClass: TComponentClass = TWebModuleMain;

implementation

uses DataModuleHtmlUnit;

{$R *.dfm}

procedure TWebModuleMain.PageProducer1HTMLTag(Sender: TObject; Tag: TTag;
  const TagString: string; TagParams: TStrings; var ReplaceText: string);

var 
 dm: TDataModuleHtml;

begin
  dm := TDataModuleHtml.Create(nil);
  try

    if SameText(TagString, 'doctitle') then
      ReplaceText := dm.GetTitle

    else if SameText(TagString, 'docbody') then
      ReplaceText := dm.GetBody

    else
      ReplaceText := '';

  finally
    dm.Free;
  end;
end;

end.

そして、これがクライアントプロジェクトへ追加された新しいデータモジュールクラスの実装です。

unit DataModuleHtmlUnit;

interface

uses
  SysUtils, Classes;

type
  TDataModuleHtml = class(TDataModule)
  private
    { Private declarations }
  public
    function GetTitle: string;
    function GetBody: string;
  end;

//var
//  DataModuleHtml: TDataModuleHtml;

implementation

{$R *.dfm}

{ TDataModuleHtml }

function TDataModuleHtml.GetTitle: string;
begin
  Result := 'WebBroker jQueryMobile Boilerplate';
end;

function TDataModuleHtml.GetBody: string;
begin
  Result :=

 '<div data-role="page">'

+'  <div data-role="header">'
+'       <h1>Page Title</h1>'
+'  </div><!-- /header -->'

+'  <div data-role="content">'
+'       <p>Page content goes here.</p>'
+'  </div><!-- /content -->'

+'  <div data-role="footer">'
+'       <h4>Page Footer</h4>'
+'  </div><!-- /footer -->'

+'</div><!-- /page -->'

end;

end.

Web アプリケーションを起動すると、依然として前と同じ jQueryMobile ボイラープレートページを表示することを確認できるはずです。

リファクタリングの全体的な考えは、その動作を変えることなく、コードの基本構造を変えることです。DataSnap サーバーからのデータに基づいて “GetBody” の実装を行い、実際の複雑な動作を処理する準備が整いました。

    まとめ

この DelphiLabs の DataSnap チュートリアルでは、モバイルデバイス上でデータベースのデータを表示を行う多層でスケーラブルなシステムを構築する実践的な手順をご覧頂きました。

最初のパートでは、Web アプリケーション構築のための Delphi WebBroker フレームワークについて検討し、WebBroker jQueryMobile ボイラープレートページを構築するためにも同じく Delphi を使用しました。

動的 HTML マークアップ生成のコードのリファクタリングを行った後、チュートリアルの2番目のパートの準備が行いました。そこでは、サンプルの InterBase データベースから顧客データを返却する DataSnap サーバーを構築し、それを jQueryMobile Web フロントエンドを動的に構築するために使用します。

このチュートリアルでは、jQueryMobile ライブラリのプレリリースバージョンを使用しており、最終的な機能は、ここで記載されているものと異なる可能性があることに注意してください。

DataSnap は Delphi, C++Builder, RAD Studio の Enterprise および Architect エディションの機能です。Starter および Professional エディションには搭載されておりません。

    参考資料


Delphi Labs: DataSnap XE - jQueryMobile Web Frontend

Written by Paweł Głowacki on . Posted in ENTERPRISE

    Introduction

In this lab exercise we are going to use Delphi XE to build a web application for displaying database data coming from a DataSnap server using jQuery Mobile JavaScript library.

At the time of this writing the jQuery Mobile is still in its alpha phase of development, so the functionality available today (March 2011), may differ from the final release.

In this article I am using jQuery Mobile Alpha 3 release available at http://jquerymobile.com/demos/1.0a3/.

The end-result of this tutorial is a sample, multitier, scalable DataSnap system consisting of a Delphi XE DataSnap standalone data server providing data from the underlying InterBase XE sample “Employees” database and a client - a WebBroker Delphi XE standalone web application that will use jQuery Mobile for displaying data.

The starting point for this tutorial is “WebBroker jQueryMobile Boilerplate” project discussed in the previous Delphi Labs episode (http://edn.embarcadero.com/article/41322).

Hide image
Click to see full-sized image

expand view>>

    Understanding jQueryMobile

Coding in pure JavaScript can be very tedious and there are plenty of frameworks and libraries to make this task easier. One of the most popular JavaScript libraries is jQuery (http://jquery.com/), which is “a fast and concise” and “simplifies HTML document traversing, event handling, animating, and Ajax interactions for rapid web development”.

One of the most recent projects within the world of jQuery is a “mobile” version of this popular library that is optimized for web browsers available on smartphones, like iPhone, Android and others. It is a layer on top of the jQuery core that is using the latest web standards, like HTML5 and CSS3, for building web pages that have this “mobile” look-and-feel.

“WebBroker JQueryMobile Boilerplate” project available at the Embarcadero Code Central (http://cc.embarcadero.com/item/28254) is just the starting point. Now it’s the time to build a DataSnap server application that will provide client data to our jQueryMobile to be displayed dynamically.

    Building the Data Server Application

We are going to create a standalone Delphi DataSnap VCL Forms application server that will read data from the InterBase XE sample “Employees” database “Customers” table. We are going to make sure that customer records are sorted by “company name”, so we do not need to perform sorting on the client side.

Right-click on the project group node in the Project Manager and select “Add New Project” and select “DataSnap Server” icon from the “Delphi Projects – DataSnap Server” category in the “New Items” dialog.

Hide image
New DataSnap Server wizard

Keep the default “VCL Forms Application” on the first screen of the wizard.

On the second screen uncheck the option for generating sample methods as we are not going to use them.

Hide image
Click to see full-sized image

On the next screen keep the default port number.

expand view>>

It is critical to make sure that you select the last, not default server methods ancestor class, which is “TDSServerModule”.

Hide image
Click to see full-sized image

Click on “Finish” button.

expand view>>

Click on “File – Save All” menu option. Save the main form unit as “FormServerUnit”, keep the default names for server container unit and server methods unit. Save the project as “DataServerApp” and the project group as “jQueryScalableSystem”.

Now we need to configure database connection to InterBase sample database.

In the “Data Explorer” window select “INTERBASE” node and right click to add a new connection and give it a name “IBEMPLOYEE”.

Right click on the “IBEMPLOYEE” connection and select “Modify Connection” option.

Enter the path to the database file, which is the default InterBase XE installation: “C:\Embarcadero\InterBase\examples\database\employee.ib”.

Click on the “Test Connection” button to make sure that the database server is running and the connection can be established.

The implementation of our Data Server will not require any coding. We are going to use a “TSQLConnection” component which provides access to a selected RDBMS instance, a “TSQLQuery” component with the query to return customer information from the database ordered by company name, and a “TDataSetProvider” component, that our client application will be connecting to.

To quickly add “TSQLConnection” component to the server methods module, just drag it from the Data Explorer onto the server methods unit. This is the fastest way as all necessary properties are automatically configured for you. Alternatively you can manually add “TSQLConnection” component and set its properties accordingly.

The actual database connection information is now embedded in “Params” property of the database connection object.

Press F6 to display “IDE Insight” and add a “TSQLQuery” component to the server methods unit.

Set its “SQLConnection” property to point to the connection object already on the data module.

Now we need to provide SQL statement for retrieving customers from “CUSTOMERS” table ordered by the company name.

Right click on the “IBEMPLOYEE” database in the Data Explorer and select “SQL Window” to author the SQL statement interactively.

Drag “CUSTOMERS” table from the list of the right hand side onto the main window in the middle.

Check all the fields in the table to include them in the query.

Select “Ascending” sorting order for the “CUSTOMER” field that contains the company name.

Select the text of the query and copy it to the clipboard right clicking on the selection and selecting “Copy”.

Close the “SQL Window”.

Hide image
Click to see full-sized image

Open “SQL” property of the query component and paste the SQL statement there.

expand view>>

Click on “Active” property to see if the SQL statement is correct and make sure to uncheck this property after verifying that it can be set to “true”.

Add “TDataSetProvider” component to the module.

Rename the provider component to “dspCustomersByCompanyName”.

Set its “DataSet” property to point to “sqlqCustomersByCompanyName” query component.

Hide image
Click to see full-sized image

That’s it! Our Delphi DataSnap data server is now ready.

expand view>>

Click on the green triangle icon to run the server application.

It has to be running until the end of this tutorial, so we could finish developing the client app and run it.

    Adding DataSnap client data access

If you see right now the code of the employees’ data module in the code editor, just press F12 to switch to the form designer view where you can add non-visual components to the data module. Unlike VCL Forms data modules are not designed for displaying their content. They have a surface only at design time that make it possible to drop different non-visual components, typically for data access functionality.

Press F6 to display “IDE Insight”. Just start typing “TSQLConnection” to find it and then add a connection to our employees data.

I have renamed this new connection to “DSSQLConnectionEmployees” to help it stand out;-)

It is always a good idea to uncheck “LoginPrompt” property at this moment, so there is no “Login” dialog displayed.

Change its “Driver” property to “DataSnap”. This will open access to sub-properties in “Driver” property specific for DataSnap connectivity. I am going to keep “localhost” as “HostName” property as I am running both applications on the same computer. In a more realistic scenario it would be some IP address of the actual machine or a DNS name.

OK… How do we connect to the remote data server now?

On the server side there is a “TDataSetProvider” component on the form ready to receive client requests from VCL “TClientDataSet” components.

The base class for the server side implementation that we have chosen in the wizard was ”TDSServerModule” that implements “IAppServer” interface from MIDAS times. This interface contains methods that data provider and client dataset components know how to use in order to send data packets back and forth over the network.

In this scenario there is no need of generating client proxy code to access the server, because the server-side functionality is already defined by “IAppServer” interface.

  IAppServer = interface(IDispatch)
    ['{1AEFCC20-7A24-11D2-98B0-C69BEB4B5B6D}']
    function  AS_ApplyUpdates(const ProviderName: WideString; Delta: OleVariant;
                              MaxErrors: Integer; out ErrorCount: Integer; var OwnerData: OleVariant): OleVariant; safecall;
    function  AS_GetRecords(const ProviderName: WideString; Count: Integer; out RecsOut: Integer;
                            Options: Integer; const CommandText: WideString;
                            var Params: OleVariant; var OwnerData: OleVariant): OleVariant; safecall;
    function  AS_DataRequest(const ProviderName: WideString; Data: OleVariant): OleVariant; safecall;
    function  AS_GetProviderNames: OleVariant; safecall;
    function  AS_GetParams(const ProviderName: WideString; var OwnerData: OleVariant): OleVariant; safecall;
    function  AS_RowRequest(const ProviderName: WideString; Row: OleVariant; RequestType: Integer;
                            var OwnerData: OleVariant): OleVariant; safecall;
    procedure AS_Execute(const ProviderName: WideString; const CommandText: WideString;
                         var Params: OleVariant; var OwnerData: OleVariant); safecall;
  end;

Add a “TDSProviderConnection” component to the data module.

I have renamed it to “DSProviderConnectionEmployees” and set its “SQLConnection” property to our connection component “DSSQLConnectionEmployees”.

It would be nice if you could now select the name of available server classes from a dropdown right now, but it is necessary to enter the actual name of the server class on the server we want to connect with. In our case this is: “TServerMethodsIBEmployees”.

Add “TClientDataSet” component to the data module.

Set its “RemoteServer” property to “DSProviderConnectionEmployees” and if everything is properly configured and connected you should see dataset provider names available as a dropdown in the “ProviderName” property.

Note that you can have multiple providers on the server and multiple provider connection components sharing the same connection to the server. Select “dspCustomersByCompanyName” as the provider name.

Rename the client dataset component to “cdsIBCustomers” and try set its “Active” property to “true” to make sure that everything is properly connected but keep it set to “false”. We do not want it to be left “Active” at design-time. We will just make it active when we need data and inactive again afterwards.

In order to simplify data access code it is a good idea to persist client dataset fields. Just double-click on the “cdsIBCustomers” client dataset to display its “Fields Editor” dialog. Right click in the dialog and select “Add All Fields”.

Now we need to provide the actual implementation of the “GetHtmlBody” method that will open the client dataset, take customer data from the client and format it as HTML using jQueryMobile specific features.

    Generating jQueryMobile code dynamically in Delphi

My main focus in this tutorial is Delphi and DataSnap, so I am not going to go into the details of jQueryMobile.

There are plenty of interesting and informative jQueryMobile resources in the web. I have found very useful “Simple development with jQuery Mobile” multimedia tutorial from http://net.tutsplus.com that was included in the Feb2011 issue of “Practical Web Design” magazine. The other resource that provides basic info on jQueryMobile that I have found was this tutorial: http://miamicoder.com/2011/creating-a-website-using-jquery-mobile-part1/.

Please find below the source code for the final version of the data module responsible for dynamic creation of our HTML page.

Just select the whole code below, copy and paste into Delphi editor.

unit DataModuleEmployeesUnit;

interface

uses
  SysUtils, Classes, DBXDataSnap, DBXCommon, DB, SqlExpr, DBClient, DSConnect;

type
  TCustomerData = record
    CUST_NO: Integer;
    CUSTOMER: String;
    CONTACT_FIRST: String;
    CONTACT_LAST: String;
    PHONE_NO: String;
    ADDRESS_LINE1: String;
    ADDRESS_LINE2: String;
    CITY: String;
    STATE_PROVINCE: String;
    COUNTRY: String;
    POSTAL_CODE: String;
    ON_HOLD: String;
  end;

  TDataModuleEmployees = class(TDataModule)
    DSSQLConnectionEmployees: TSQLConnection;
    DSProviderConnectionEmployees: TDSProviderConnection;
    cdsIBCustomers: TClientDataSet;
    cdsIBCustomersCUST_NO: TIntegerField;
    cdsIBCustomersCUSTOMER: TStringField;
    cdsIBCustomersPHONE_NO: TStringField;
    cdsIBCustomersCONTACT_LAST: TStringField;
    cdsIBCustomersCONTACT_FIRST: TStringField;
    cdsIBCustomersADDRESS_LINE1: TStringField;
    cdsIBCustomersADDRESS_LINE2: TStringField;
    cdsIBCustomersCITY: TStringField;
    cdsIBCustomersSTATE_PROVINCE: TStringField;
    cdsIBCustomersCOUNTRY: TStringField;
    cdsIBCustomersPOSTAL_CODE: TStringField;
    cdsIBCustomersON_HOLD: TStringField;
  private
    FCustDetailPages: string;
    function GetCustList: string;
    function GetCustDetailPages: string;
    procedure OutputCustomerDetailPage(const c: TCustomerData);
  public
    function GetHtmlBody: string;
  end;

//var
//  DataModuleEmployees: TDataModuleEmployees;

implementation

{$R *.dfm}

{ TDataModuleEmployees }

function TDataModuleEmployees.GetHtmlBody: string;
begin
  Result :=
 '<!-- Start of main page -->'
+'<div data-role="page" id="main">'

+'  <div data-role="header">'
+'       <h1>Delphi in the Cloud</h1>'
+'  </div><!-- /header -->'

+'  <div data-role="content">'

+'<h2>Welcome to DelphiLabs!</h2>'
+'<p>Press on the button below to find information'
+' about customers of a fictional company.</p>'

+'        <ul data-role="listview" data-theme="c" data-inset="true">'
+'          <li><a href="#customersMain"></a>Customers</li>'
+'          <li><a href="#about"></a>About</li>'
+'        </ul>'
+'  </div><!-- /content -->'

+'  <div data-role="footer">'
+'       <h6>DelphiLabs DataSnap Tutorial</h6>'
+'  </div><!-- /footer -->'
+'</div><!-- /page -->'

+'<!-- Start of about page -->'
+'<div data-role="page" id="about">'

+'  <div data-role="header">'
+'       <h1>About</h1>'
+'  </div><!-- /header -->'

+'  <div data-role="content">'

+'<h2>Welcome to DelphiLabs!</h2>'
+'<p>The page you just see has been created as a demo'
+' project for DelphiLabs "DataSnap" jQueryMobile tutorial'
+' and deployed to a virtual machine running in <a href="http://aws.amazon.com/">Amazon EC2</a>.</p>'
+'<p>Sample "Customers" data comes from <a href="http://www.embarcadero.com/products/interbase">InterBase XE</a> demo database'
+' that is accessed through <a href="http://www.embarcadero.com/products/delphi">Delphi XE</a> standalone DataSnap server application.</p>'
+'<p>The HTML markup that you just see right now has been generated dynamically in Delphi code'
+' using <a href="http://jquerymobile.com/2011/02/jquery-mobile-alpha-3-released/">jQueryMobile (alpha3)</a>.'
+'<hr/>'
+'<p>Visit <a href="http://www.embarcadero.com/rad-in-action/delphi-labs">'
+'Embarcadero RAD-in-Action Delphi-Labs page</a> for more details!</p>'
+'  </div><!-- /content -->'
+'  <div data-role="footer">'
+'       <h6>DelphiLabs DataSnap Tutorial</h6>'
+'  </div><!-- /footer -->'
+'</div><!-- /page -->'


+'<!-- Start of customers summary page -->'
+'<div data-role="page" id="customersMain">'

+'  <div data-role="header">'
+'       <h1>Customers</h1>'
+'  </div><!-- /header -->'

+'  <div data-role="content" data-theme="b">'

+ GetCustList

+'  </div><!-- /content -->'
+'  <div data-role="footer">'
+'       <h6>DelphiLabs DataSnap Tutorial</h6>'
+'  </div><!-- /footer -->'

+'</div><!-- /page -->'

+ GetCustDetailPages

end;

function TDataModuleEmployees.GetCustList: string;
var s: string; ch: char; c: TCustomerData;
begin
  s := '<ul data-role="listview" data-inset="true" data-theme="c" data-filter="true">';

  ch := ' ';

  cdsIBCustomers.Active := true;
  try
    while not cdsIBCustomers.Eof do
    begin
      c.CUST_NO := cdsIBCustomersCUST_NO.AsInteger;
      c.CUSTOMER := cdsIBCustomersCUSTOMER.AsString;
      c.CONTACT_FIRST := cdsIBCustomersCONTACT_FIRST.AsString;
      c.CONTACT_LAST := cdsIBCustomersCONTACT_LAST.AsString;
      c.PHONE_NO := cdsIBCustomersPHONE_NO.AsString;
      c.ADDRESS_LINE1 := cdsIBCustomersADDRESS_LINE1.AsString;
      c.ADDRESS_LINE2 := cdsIBCustomersADDRESS_LINE2.AsString;
      c.CITY := cdsIBCustomersCITY.AsString;
      c.STATE_PROVINCE := cdsIBCustomersSTATE_PROVINCE.AsString;
      c.COUNTRY := cdsIBCustomersCOUNTRY.AsString;
      c.POSTAL_CODE := cdsIBCustomersPOSTAL_CODE.AsString;
      c.ON_HOLD := cdsIBCustomersON_HOLD.AsString;


      if ch <> c.CUSTOMER[1] then
      begin
        ch := c.CUSTOMER[1];
        s := s + '<li data-role="list-divider" data-theme="b">' + ch + '</li>';
      end;

      s := s +
      '<li><a href="#cust' + IntToStr(c.CUST_NO) + '">' + cdsIBCustomersCUSTOMER.AsString + '</a></li>';

      OutputCustomerDetailPage(c);

      cdsIBCustomers.Next;
    end;
  finally
    cdsIBCustomers.Active := false;
  end;

  s := s + '</ul>';
  Result := s;

end;

function TDataModuleEmployees.GetCustDetailPages: string;
begin
  Result := FCustDetailPages;
end;

procedure TDataModuleEmployees.OutputCustomerDetailPage(const c: TCustomerData);
var s: string; aStatus: string;
begin
  if c.ON_HOLD <> '' then
    aStatus := '<B> (ON HOLD)</B>'
  else
    aStatus := '';

  s :=
 '<!-- Start of customer detail page -->'
+'<div data-role="page" id="cust' + IntToStr(c.CUST_NO) + '">'

+'  <div data-role="header">'
+'       <h1>Customer Details</h1>'
+'  </div><!-- /header -->'
+'  <div data-role="content">'

+'<p><h1>' + c.CUSTOMER + aStatus + '</h1></p>'

+'<div data-role="collapsible" data-theme="b">'
+'<h3>Contact</h3>'
+'<p><h2>' + c.CONTACT_FIRST + ' ' + c.CONTACT_LAST + '</h2></p>'
+'<p>' + c.PHONE_NO + '</p>'
+'</div>'

+'<div data-role="collapsible" data-theme="b">'
+'<h3>Address</h3>'
+'<p>' + c.ADDRESS_LINE1 + '</p>'
+'<p>' + c.ADDRESS_LINE2 + '</p>'
+'<p>' + c.CITY + '</p>'
+'<p>' + c.POSTAL_CODE + ' ' + c.CITY +'</p>'
+'<p>' + c.STATE_PROVINCE + '</p>'
+'<p><b>' + c.COUNTRY + '</b></p>'
+'</div>'

+'  </div><!-- /content -->'
+'  <div data-role="footer">'
+'       <h6>DelphiLabs DataSnap Tutorial</h6>'
+'  </div><!-- /footer -->'

+'</div><!-- /page -->';

  FCustDetailPages := FCustDetailPages + s;
end;

end.

The running version of this web application can be accessed through this URL: http://79.125.25.31:8080/

In my Chrome web browser it looks like this:

Hide image
Click to see full-sized image

If you click on “Customers” button we will have a nice transition to customer listing ordered by company name. Check out the filter field at the top.

expand view>>

Hide image
Click to see full-sized image

If you click on the customer name, you should see more details.

expand view>>

Hide image
Click to see full-sized image

That’s it! Feel free to use techniques described here to build your own, great looking web sites for mobile devices in Delphi XE!

expand view>>

    Summary

In this Delphi Labs DataSnap tutorial we have looked into practical steps of building a multitier, scalable system for displaying database data on mobile devices. We have used InterBase XE database as a source of data, intermediate standalone DataSnap server for data access and WebBroker web application for displaying data using modern jQueryMobile JavaScript libraries optimized for mobile web browsers.

Note that we are using a pre-release version of jQueryMobile library, so the final functionality may differ from what has been described here.

DataSnap is a feature of Delphi, C++Builder and RAD Studio Enterprise and Architect editions. It is not available in Starter and Professional editions.

    References

    A Little Disclaimer

In “Delphi Labs” I’m focusing on Delphi development, but all of the applications described here, could be also built using C++Builder, which is a part of RAD Studio.

Delphi and C++Builder are deeply integrated. Both are different “personalities” of the same underlying Integrated Development Environment (IDE) and they share the Visual Component Library (VCL).

It is just my personal preference to use Delphi Pascal programming language. If you are more comfortable with C++ you should definitely give the C++Builder XE (http://www.embarcadero.com/products/cbuilder) a try!


Check out more tips and tricks in this development video: