Sunday, February 21, 2010

 

Web 2.0 programming with Object Pascal (Part 1)


I'm sure you experience the same feeling as me, when
a "web developer" looks at your computer screen
saying "wow! you program in Delphi, I used to use it
before programming web applications". When I hear that,
I want to start telling them that Delphi (Object Pascal)
can help you get better results than any other Web programming
language.

I hope this series of articles let you know what can
be done in the web scene with Delphi and FreePascal,
and, if you are a PHP-Python-Ruby-ASP-[place your language here] programmer,
just take a look at what can be done with modern Object Pascal languages.

Part 1 - CGI + ExtJs

Before continuing, please read what is CGI, and also take a look at ExtJs.

From now on, I'll create a CGI program capable of creating
JSON responses to an ExtJs frontend.

This example, will be compiled with FreePascal 2.5.1. The CGI
framework I'll use will be fcl-web, but you can choose
PowTils and EzCGI as well. If you
choose Delphi to create the CGI program, you can use WebBroker,
an excelent web development framework.

All the examples of this series will be hosted on an Apache 2
server
, but of course, you can
use any CGI capable server like IIS, LightHttpd (impressive!) and others.

To test this example, I'll use Ubuntu 9.10 64bits and
FreePascal 2.5.1 trunk version, and ExtJs 3.1.1.

The client

As a starting point for this example, I'll create
a simple ExtJs GridPanel placed on a static HTML page. The
data shown on the grid, will be loaded from a JSon file,
so no interaction with our CGI app here.

Now lets create a simple HTML page containing an
ExtJs grid, copy and paste the code below and name it
grid1.html, and save it in /var/www/samples directory:


<html>
<head>
<meta equiv="Content-Type" content="text/html; charset=utf-8">
<title>Grid Example 1</title>
<link rel="stylesheet" type="text/css" href="/extjs/resources/css/ext-all.css">
<script type="text/javascript" src="/extjs/adapter/ext/ext-base.js"></script>
<script type="text/javascript" src="/extjs/ext-all.js"></script>
</head>
<body>
<script type="text/javascript" src="grid1.js"></script>
<h1>Grid Example 1</h1>
<div id="grid1"></div>
</body>
</html>


Looking at the HTML code above, you'll note in many places there are
references to "/extjs", that means I have uncompressed the ExtJs
files into /var/www directory. My Apache2's document root directory
is /var/www, and my ExtJs files are under /var/www/extjs, also
in the line just below the tag, there is a reference to
"grid1.js", that reference points to that file placed in the same
directory as "grid.html".

This is the content of grid1.js (also saved in /samples directory):


Ext.onReady(function(){

var dataStore = new Ext.data.JsonStore({
url: '/samples/customerslist.json',
root: 'rows',
method: 'GET',
fields: [
{name: 'firstname', type: 'string'},
{name: 'lastname', type: 'string'},
{name: 'age', type: 'int'},
{name: 'phone', type: 'string'}
]
});

var myGrid1 = new Ext.grid.GridPanel({
id: 'customerslist',
store: dataStore,
columns: [
{id: "firstname", header: "First Name", width: 100, dataIndex: "firstname", sortable: true},
{header: "Last Name", width: 100, dataIndex: "lastname", sortable: true},
{header: "Age", width: 100, dataIndex: "age", sortable: true},
{header: "Phone", width: 100, dataIndex: "phone", sortable: true}
],
autoLoad: false,
stripeRows: true,
autoHeight: true,
width: 400
});

dataStore.load();

myGrid1.render('grid1');
});


This file creates an instance of Ext.data.JsonStore, the object
in charge of loading data from an external JSon source, in this
case the file "/samples/customerslist.json". Also, an
instance of Ext.grid.GridPanel called myGrid1 is created, that
is the Grid who'll contain the data loaded from the JsonStore,
the last line of this file, tells where should render the grid,
in this case on a Div with an ID named "grid1" (look at grid1.html).

Please, play with this example by adding more data to the Json file,
and change the properties of the grid before continuing.

The result:




Dynamic JSon data

The next step, is to create a CGI application in charge of
responding requests from the JSonStore. This is a simple
task for our CGI program, just create a string containing
the same data as "customerslist.json" file and send to the client.

The first FPC CGI application:


program cgiproject1;

{$mode objfpc}{$H+}

uses
Classes,SysUtils,httpDefs,custcgi;

Type
TCGIApp = Class(TCustomCGIApplication)
Public
Procedure HandleRequest(ARequest : Trequest; AResponse : TResponse); override;
end;

Procedure TCGIApp.HandleRequest(ARequest : Trequest; AResponse : TResponse);
begin
AResponse.Content := 'Hello!';
end;

begin
With TCGIApp.Create(Nil) do
try
Initialize;
Run;
finally
Free;
end;
end.


Save it as "customerslist.pp" then compile with "fpc customerslist.pp".
Then, copy it to /var/www/cgi-bin and open a web browser, then point it to
"http://localhost/cgi-bin/customerslist". It should show "Hello!". That's it.

Now, to let this program respond with the needed JSon, just replace
the line that AResponse.Content := 'Hello!'; with this:


AResponse.Content :=
'{' +
' "rows": [ ' +
' {"firstname": "Leonardo", "lastname": "Ramé", "age": 35, "phone": "1234556"}, ' +
' {"firstname": "John", "lastname": "Williams", "age": 15, "phone": "435233"}, ' +
' {"firstname": "Cecilia", "lastname": "Strada", "age": 28, "phone": "423234"}, ' +
' {"firstname": "Gary", "lastname": "Thomson", "age": 43, "phone": "123"} ' +
' ] ' +
'}';


Compile, copy to /var/www/cgi-bin directoy and test.

To call it from the JsonStore, go back to the grid1.js file,
and replace the property url of the JsonStore by "/var/www/cgi-bin/customerslist".

Note: if you are working on Windows, the file "customerslist" will be created
as "customerslist.exe", so you must replace it in every reference to
"/var/www/cgi-bin/customerslist".

If you don't like to create JSON responses as simple strings,
you can use the framework fcl-json, to create them using
an object-oriented method.

To adapt the example for usin fcl-json, you must add the
unit fpjson to the uses clause, and replace
the TCGIApp.HandleRequest by this:


procedure TCGIApp.HandleRequest(ARequest : Trequest; AResponse : TResponse);
var
lPerson1: TJSONObject;
lPerson2: TJSONObject;
lPerson3: TJSONObject;
lPerson4: TJSONObject;
lJson: TJSONObject;
lJsonArray: TJSONArray;

begin
lPerson1 := TJSONObject.Create;
lPerson2 := TJSONObject.Create;
lPerson3 := TJSONObject.Create;
lPerson4 := TJSONObject.Create;

lJsonArray := TJSONArray.Create;
lJson := TJSONObject.Create;
try
(* populate lPerson1 *)
lPerson1.Add('firstname', TJsonString.Create('Leonardo'));
lPerson1.Add('lastname', TJsonString.Create('Ramé'));
lPerson1.Add('age', TJSONIntegerNumber.Create(35));
lPerson1.Add('phone', TJsonString.Create('1234567'));
(* populate lPerson2 *)
lPerson2.Add('firstname', TJsonString.Create('John'));
lPerson2.Add('lastname', TJsonString.Create('Williams'));
lPerson2.Add('age', TJSONIntegerNumber.Create(15));
lPerson2.Add('phone', TJsonString.Create('14567'));
(* populate lPerson3 *)
lPerson3.Add('firstname', TJsonString.Create('Cecilia'));
lPerson3.Add('lastname', TJsonString.Create('Strada'));
lPerson3.Add('age', TJSONIntegerNumber.Create(29));
lPerson3.Add('phone', TJsonString.Create('34567'));
(* populate lPerson4 *)
lPerson4.Add('firstname', TJsonString.Create('Gary'));
lPerson4.Add('lastname', TJsonString.Create('Thomson'));
lPerson4.Add('age', TJSONIntegerNumber.Create(43));
lPerson4.Add('phone', TJsonString.Create('344567'));

(* Fill the array *)
lJsonArray.Add(lPerson1);
lJsonArray.Add(lPerson2);
lJsonArray.Add(lPerson3);
lJsonArray.Add(lPerson4);

(* Add the array to rows property *)
lJson.Add('rows', lJsonArray);

AResponse.Content := lJson.AsJSON;
finally
lJson.Free;
end;
end;


This looks like too much to type when comparing to the other method,
but it's more readable, and easier to deal with complex JSon data.

Showing data from a database

Now, I'll replace the static JSon data with a dynamic one,
resulting from a database query.

In this example, I'll connect to a Firebird 2.1 database server,
running on the same machine I'm creating the example. To connect
to the database, I'll use fcl-db framework and its TIBConnection class.

This is the final project of this part, a dynamic-driven
web page containing an ExtJs Grid populated with data
coming from a databse.


program cgiproject1;

{$mode objfpc}{$H+}

uses
Classes,SysUtils,
httpDefs,custcgi, // needed for creating CGI applications
fpjson, // needed for dealing with JSon data
Db, SqlDb, ibconnection; // needed for connecting to Firebird/Interbase;

Type
TCGIApp = Class(TCustomCGIApplication)
Private
FConn: TSqlConnection;
FQuery: TSqlQuery;
FTransaction: TSqlTransaction;
procedure ConnectToDataBase;
Public
Procedure HandleRequest(ARequest : Trequest; AResponse : TResponse); override;
end;

procedure TCGIApp.ConnectToDataBase;
begin
FConn := TIBConnection.Create(nil);
FQuery := TSqlQuery.Create(nil);
FTransaction := TSqlTransaction.Create(nil);
with FConn do
begin
DatabaseName := 'TEST-DATABASE';
UserName := 'SYSDBA';
Password := 'masterkey';
HostName := 'localhost';
Connected := True;
Transaction := FTransaction;
FQuery.Database := FConn;
end;
end;

Procedure TCGIApp.HandleRequest(ARequest : Trequest; AResponse : TResponse);
var
lPerson: TJSONObject;
lJson: TJSONObject;
lJsonArray: TJSONArray;

begin
(* Query the database *)
FQuery.Sql.Text := 'select * from people';
FQuery.Open;
FQuery.First;

lJsonArray := TJSONArray.Create;
lJson := TJSONObject.Create;
try
while not FQuery.Eof do
begin
lPerson := TJSONObject.Create;
lPerson.Add('firstname', TJsonString.Create(FQuery.FieldByName('FirstName').AsString));
lPerson.Add('lastname', TJsonString.Create(FQuery.FieldByName('LastName').AsString));
lPerson.Add('age', TJSONIntegerNumber.Create(FQuery.FieldByName('Age').AsInteger));
lPerson.Add('phone', TJsonString.Create(FQuery.FieldByName('Phone').AsString));
FQuery.Next;
(* Fill the array *)
lJsonArray.Add(lPerson);
end;
(* Add the array to rows property *)
lJson.Add('rows', lJsonArray);
AResponse.Content := lJson.AsJSON;
finally
lJson.Free;
end;
end;

begin
With TCGIApp.Create(Nil) do
try
Initialize;
ConnectToDatabase;
Run;
finally
Free;
end;
end.


The second part of this series, will focus on
adding some CRUD (create, read, update and delete) functions
to the application.

Click here to download the source files

I hope you enjoy this article as much as I did writing it.

This page is powered by Blogger. Isn't yours?