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.
Comments:
nice post Leonardo.
I'm using Delphi for some (heavy calculation) web backend.
With DataSnap 2010 you can create REST webservice too.
 
Yes, you are right Daniele, I'll try to add a DataSnap 2010 example later in this series.
 
Very nice post, Leonardo. can you post the source code files?
 
I am what you call a "web developer", and am doing my web development using jQuery for all javascript/ajax goodness, and java/jsp on the server.

This post does not make me jealous, no it tells me that I chose the right server-framework.

http://www.json.org/java/ is a nice help to create json on the server.

Especially since I can also make use of the org.json package for Android.
 
You should take a look at ExtPascal (http://code.google.com/p/extpascal/), it’s a wrapper/binding for ExtJS so you can write a complete web application in pure object pascal.
 
The main problem in using Delphi as a web tool is hosting: most hosts doesn't allow native code at all - just script. Some allow Java/ASP.NET (since it runs in it's sandboxes) because it can be killed without killing the webserver (which can be hosting a lot of other webapplications).
 
Leonardo, first, great post for start.

Second, well, honestly i find like the anonymous one.
What a chunk of code, mixed technologies to get a simple grid on web 2.0!

I really feel uncomfortable with that kind of stuff, even if my loved pascal is behind.

You can use a hammer to make a hole in a wall, but is the right tool for the task?

If you wanna work on web 2.0 using pascal, please take a look at Morfik (www.morfik.com).

Feel a more rad and intuitive *delphi like* than this strange mix of stuff.

It have a FPC compiler behind (also a delphi compiler for win32) so it evens compile pascal code and libraries inside the server.

And if you don't care about the tool colors, shapes, etc, just try django, amazing tool for easy web producing (and open source).

Best regards.
 
@Rodrigo, I added a link at the bottom of the post with a .rar containing all the files.

@Anonymous. I preffer native code over Java and .Net, it's true that Java is faster than in the past, but I don't see any real advantage of it over Object Pascal. Also in my particular case, I have many applications already written in Delphi/FreePascal and code reuse is very important in that case.

Regarding Android, if I'd write desktop apps for it, I'd choose Java as you do, of course.

@Gilberto. I'll try to write an article about it. I used it in the past, and it's really nice to work with it. The only problem I had recently, was it doesn't work with FPC 2.5.1, maybe this problem is fixed right now.

@Fabricio. That's not true, many of the cheapest web hosting services allow CGI. It's easier to find a web hosting service that allow servicing CGI apps rather than JSP, for example.

@Donald. I tried Morfik, but apart from its cost, I didn't understand its philosophy, also when using it I felt like losing control of the whole development process. I think it's a matter of taste in the end.

About your comment "what a chunk of code", this is just raw code, I could have created some classes to minimize it. In my "real" work, I use templates (similar to JSP) and ORM to database access.
 
I'm not a web dev, so I'm not much about it, but what about using FastCGI instead? It's supposed to be faster and may fit with Delphi better than CGI.
 
@RompeTecla. In a future article I'll write about it, remember this is just the Part 1 of this series of Web 2.0 programming with Object Pascal.
 
Thanks Leonardo! This looks really interesting. Time to expand my horizon beyond Delphi desktop applications!
 
To Mike,
Apreciaria ampliara sus comentarios sobre morfik Gracias

asapltda@yahoo.com
 
If everyone is here promoting his favorite tools for creating web applications, then I must talk about Delphi On Rails. This framework is strongly inspired by Ruby on Rails and his philosophy.
benefits: Opensource,REST,MVC,JSON, Conventions over Configurations ...

you can do everything mentioned in this article in much less code.

http://www.delphionrails.com
 
Leonardo wrote:
That's not true, many of the cheapest web hosting services allow CGI. It's easier to find a web hosting service that allow servicing CGI apps rather than JSP, for example.


When I googled about hosting, after your response (which I read by email), most support for for CGI hosting is just about *SCRIPTS*, no one talking about native code.
 
@Leonardo, since we are talking about freepascal, I would also suggest to use a parser/serializer JSON easier to use:
http://code.google.com/p/superobject
 
Thank you, Leonardo. Very nice and interesting post. I look forward to your further installments on this subject. A bit off-topic but I would appreciate if you would comment or share your experience with FreePascal. With regards to stability, in particular. I love using Delphi. But as I am more and more confronted with that Delphi lacks a 64-bit compiler with respect to development of 64-bit versions of IIS extensions, shell extensions and, soon, add-ins for Office 2010, I'm looking for alternatives.
 
@Jesse

I used FreePascal with the Lazarus IDE myself months ago. If you have D7, you'll find very unstable. If you have touched new Delphis (2007+), you'll also miss much interesting features. The IDE is in D3-D4 level.
 
@Fabricio. In every *cheap* web hosting I've used in the past, when they advertise CGI scripts, they supported CGI executables. At least on Linux, a CGI script is just a file with execution permissions (chmod +x), so no difference between an interpreted script and a compiled file, to the operating system, both are files that can be executed.

@Henri. Is that the same as the included in Delphi 2010?. I did't know it was based on an Open Source project. I'll give it a try, thanks.
P.S.: I used fcl-json because it's included in FPC, not because I preffer it against other alternatives.

@Jesse. I use FreePascal only for Linux server-side applications, like intranets and enterprise integration servers, a couple of them are running non stop since a year ago. On Windows I use Delphi exclusively, I didn't have any 64bits requests yet, so I can't comment on this.
 
@Fabricio and Jesse. I don't do professional work with Lazarus (my IDE is VIM with a couple of Plugins), and I used to have the same impression about it as Fabricio in the past, but since november '09 I started to test the SVN Trunk version, and I really like it, I don't think it compares to D3 or D4, the IDE is very stable, and the editor is as modern as D2010, I only miss packages support in it.
 
@Leonardo, The parser in Delphi 2010 is minimalist and does not support the full syntax of JSON. Mine is much more complete, and supports paths, interfaces, simplified syntax, new RTTI, etc...
 
@Henri. Very interesting, I just went to your project's page on googlecode, and reading the wiki, I couldn't find how to compose JSon data by code, is it possible, or the user have to create some other object then convert to JSon by using RTTI?.
 
@Fabricio and Leonardo: thank you for your input. Appreciate it. I've used D7 for many, many years and I'm totally spoiled with the fact that D7 enabled me to develop rock-solid apps. I've upgraded to D2010, skipping versions in between. But I may be forced to leave Delphi for the reasons I've mentioned before. As much I like and prefer Object Pascal. Well, I guess the best thing to do is to get my feet wet, my hands dirty and take Lazarus/FPC for a spin. Thanks again for the input.
 
@Leonardo, There are several ways, this is the simplest:
obj := SO(['prop1', true, 'prop2', 'foo', 'array', SA([1,2,3])]);
obj.B['prop1'] := false;
obj.I['array[1]'] := 2;
 
Hi Leonardo: Yo estoy usando esta tecnologia, pero tengo un problema, resulta que cuando llamo a la propiedad getValue() de cualquier objeto Form (TextField, TextArea, etc. ) los espacios entre el texto original es eliminado, por ejemplo: "The text here" el resultado es: "Thetexthere". Si me puedes ayudar, te lo agradezco, saludos
 
Hi Net305, if the problem is related to ExtJs I think you'll get better answers by asking the same question in their forum.

From my side, I never faced such problem.
 
re: Morfik.
A very good web dev platform. But it tries to do too much. Although you can use Delphi to compile it, it is very difficult to use other 3rd party Delphi database engines natively (non-odbc). It would be nice if you could switch to NexusDb or ElevateDb, DBISAM etc., but just not practical. You will also loose the db design tools if you try it. But if you like FireBird and don't mind paying $600-$1000, you'll like Morfik.
 
Leonardo: Very instructive blog. The advice of taking advantage of Delphi's possibilities came at the right moment for me. I've developed several local Delphi apps. using D3, D4, D5 and D7. I'm tuning now to Web and see three ways: now, and first, your advice, to take advantage of acquired experience and continue with Delphi; start a new approach from scratch in the PHP framework (Zend or rather Symfony, that is free); or Morfik, , also met in your blog, still in Object Pascal and lovely in its aesthetic approach, but scaring if its only DB connections are ODBC. Which would you suggest as better?
 
Thanks, Emeterio.

To connect to databases with Delphi, you have the same options as in desktop applications, like DBExpress, ADO, Zeos, AnyDAC, and others.

On the FreePascal/Lazarus side, you can use FCL-DB, and Zeos, and I'm sure I'm forgetting to name a bunch of other db connectiviy libraries.
 
Leonardo,

First of all, thank you for your nice idea of combining two favorite technologies of mine: extjs and delphi.

Nontheless, there are some things that you really need to let your blog readers know about!

Assuming we are working with extjs 4 and delphi 2010 here is what we must pay attention to:

1) Your example may work with a file of json data but WILL NOT work when calling a url (e.g. an ISAPI dll). You really need an HttpProxy to do that. Here is how:

var dataStore = new Ext.data.JsonStore({
proxy: {
type: 'ajax',
url: 'cgi-bin/Test.dll/SampleJsonData',
reader: {
type: 'json',
root: 'rows'
}
},
fields: [
{name: 'firstname', type: 'string'},
{name: 'lastname', type: 'string'},
{name: 'age', type: 'int'},
{name: 'phone', type: 'string'}
]
});

2) Make sure you load the dataStore BEFORE defining the GridPanel.

3) Use the GridPanel's renderTo property (renderTo: 'grid1') to successfully render the grid on the web page.

4) Besides returning a json-formatted string the action of the ISAPI dll MUST ALSO set the content type of the response, for example:

Response.ContentType := 'application/json';

Otherwise, the extjs store is never going to work properly. This applies to whatever server method used to return json data and not only to Delphi.

Last but not least, I think you should really talk about creating a DataSnap WebBroker Application to provide the data. This will justify the title of your post, at least.

Again, thank you very much for your post but I've spent half a day to make things work as they should.
 
Variants of Pascal have also frequently been used for everything from research projects to PC games and embedded systems. Newer Pascal compilers exist which are widely used. This is great news, knowing that old programming languages are coming back for innovation.
 
Post a Comment



<< Home

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