WEB 2.0 программирование на Object Pascal. Часть 2



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

Первым делом создадим панель инструментов и расположим на ней три кнопки: btnAdd, btnEdit и btnDelete. По их нажатию мы будем добавлять, редактировать и удалять данные. Кроме панели инструментов, нам также потребуется создать новое окно (popup window). В нем мы и будем управлять данными (добавлять/редактировать).

Новая таблица (grid)

Вместо того чтобы перезаписывать файлы, используемые в прошлом проекте, я рекомендую Вам создать новую директорию с именем samples2. В эту директорию Вы будете сохранять все рассмотренные примеры кода.

Обратите внимание: файл grid2.html содержит те же самые данные, что и файл grid1.html. Поэтому напарьтесь, а просто скопипастите этот файл в директорию sample2. Только не забудьте после копирования изменить имя с grid1.html на grid2.html. Проделав это не хитрое действие, измените также id контейнера с grid1 на grid2.

Содержимое файла grid2.js:

Ext.onReady(function(){
 
  var dataStore = new Ext.data.JsonStore({
      //url: '/samples/customerslist.json',
      url: '/cgi-bin/customerslist',
      root: 'rows',
      method: 'GET',
      fields: [
        {name: 'id', type: 'int'},
        {name: 'firstname', type: 'string'},
        {name: 'lastname', type: 'string'},
        {name: 'age', type: 'int'},
        {name: 'phone', type: 'string'}
      ]
    });
 
  var btnAdd = new Ext.Toolbar.Button({
    text: 'Add',
    handler: function(){
      var win = new MyPeopleWindow();
      // to refresh the grid after Insert
      win.afterPost = function(){
        dataStore.load();
      };
     win.show();
    }
  });
 
  var btnEdit = new Ext.Toolbar.Button({
    text: 'Edit',
    handler: function(){
      var win = new MyPeopleWindow(selRecordStore.id);
      // to refresh the grid after Update
      win.afterPost = function(){
        dataStore.load();
      };
      win.show();
    }
  });
 
  var btnDelete = new Ext.Toolbar.Button({
    text: 'Delete',
    handler: function(){
      Ext.Msg.confirm(
        'Delete customer?', 
        'Are you sure to delete this customer?',
        function(btn){
          if(btn == 'yes'){
            var conn = new Ext.data.Connection();
            conn.request({
                url: '/cgi-bin/customerslist',
                method: 'POST',
                params: {"delete_person": selRecordStore.id},
                success: function(response, options) {
                    // refresh the grid after Delete
                    JSonData = Ext.util.JSON.decode(response.responseText);
                    if(JSonData.success)
                      dataStore.load();
                    else
                      Ext.Msg.alert('Status', JSonData.failure);
                },
                failure: function(response, options) {
                    Ext.Msg.alert('Status', 'An error ocurred while trying to delete this customer.');
                }
            });
          }
        }
      );
    }
  });
 
  var myGrid1 = new Ext.grid.GridPanel({
    id: 'customerslist',
    store: dataStore,
    columns: [
      {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}
    ],
    sm: new Ext.grid.RowSelectionModel({
      singleSelect: true,
      listeners: {
        rowselect: function(smObj, rowIndex, record){
          selRecordStore = record;
        }
      }
    }),
    tbar: [
      btnAdd,
      btnEdit,
      btnDelete
    ],
    autoLoad: false,
    stripeRows: true,
    height: 200,
    width: 500
  });
 
  dataStore.load();
 
  myGrid1.render('grid2');
});

Так, теперь отредактируем форму:

MyPeopleForm = Ext.extend(Ext.FormPanel, {
    initComponent: function(){
      Ext.apply(this, {
        border:false,
        labelWidth: 80,
        defaults: {
          xtype:'textfield',
          width: 150
        },
        items:[
          {xtype:'numberfield',fieldLabel:'Id',name:'id'},
          {fieldLabel:'First Name',name:'firstname'},
          {fieldLabel:'Last Name',name:'lastname'},
          {xtype:'numberfield',fieldLabel:'Age',name:'age'},
          {fieldLabel:'Phone',name:'phone'}
        ]
      });
      MyPeopleForm.superclass.initComponent.call(this, arguments);
    },
    setId: function(idPerson) {
      this.load(
        {
          method: 'POST', 
          url: '/cgi-bin/customerslist', 
          params: {'idperson': idPerson}
        }
      );
    }
  });
 
  MyPeopleWindow = Ext.extend(Ext.Window, {
    constructor: function(idPerson){
      MyPeopleWindow.superclass.constructor.call(this, this.config);
      // if idPerson is not null, then edit record
      // otherwise it's a new record
      if(idPerson != null)
        this.form.setId(idPerson);
    },
    afterPost: function(){
      this.fireEvent('afterPost', this);
    },
    initComponent: function(){
      Ext.apply(this, {
        title: 'Loading data into a form',
        bodyStyle: 'padding:10px;background-color:#fff;',
        width:300,
        height:270,
        closeAction: 'close',
        items: [ this.form = new MyPeopleForm() ],
        buttons: [
          {
            text:'Save',
            scope: this,
            handler: function(){
              this.form.getForm().submit({
                scope: this,
                url: '/cgi-bin/customerslist',
                method: 'POST',
                // here I add the param save_person
                // to let the cgi program decide
                // a course of action (save person data in this case).
                params: {'save_person':'true'},
                success: function(form, action){
                  // on success I just close the form 
                  this.afterPost();
                  this.close();
                },
                failure: function(form, action){
                  Ext.Msg.alert("Error","There was an error processing your request\n" + action.result.message);
                }
              });
            }
          },
          {
            text:'Cancel',
            handler: function(){this.close();},
            // important!, without "scope: this" 
            // calling this.close() will try to close the Button!,
            // and we need to close the Window, NOT the button.
            scope: this           
          }
        ]
      });
      MyPeopleWindow.superclass.initComponent.call(this, arguments);
    }
  });


Все, интерфейс готов. Перейдем к следующему шагу и создадим новый файл customerslist.pp, содержащий код CGI приложения:

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;
    function GetCustomersList: string;
    function GetCustomer(AIdPerson: string): string;
    procedure FillJSONObject(AJson: TJsonObject);
    function SavePerson(ARequest: TRequest): string;
    function DeletePerson(ARequest: TRequest): string;
  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 := 'TARJETA';
    UserName := 'SYSDBA';
    Password := 'masterkey';
    HostName := '192.168.1.254';
    Connected := True;
    Transaction := FTransaction;
    FQuery.Database := FConn;
  end;
end;
 
procedure TCGIApp.FillJSONObject(AJson: TJsonObject);
begin
  AJson.Add('id', TJsonIntegerNumber.Create(FQuery.FieldByName('IdCliente').AsInteger));
  AJson.Add('firstname', TJsonString.Create(FQuery.FieldByName('Apellido').AsString));
  AJson.Add('lastname', TJsonString.Create(FQuery.FieldByName('Nombres').AsString));
  AJson.Add('age', TJSONIntegerNumber.Create(FQuery.FieldByName('IdCliente').AsInteger));
  AJson.Add('phone', TJsonString.Create(FQuery.FieldByName('TelFijo').AsString));
end;
 
function TCGIApp.GetCustomersList: string;
var
  lPerson: TJSONObject;
  lJson: TJSONObject;
  lJsonArray: TJSONArray;
 
begin
  (* Query the database *)
  FQuery.Close;
  FQuery.Sql.Text := 'select * from clientes';
  FQuery.Open;
  FQuery.First;
 
  lJsonArray := TJSONArray.Create;
  lJson := TJSONObject.Create;
  try
    while not FQuery.Eof do
    begin
      lPerson := TJSONObject.Create;
      fillJsonObject(lPerson);
      FQuery.Next;
      (* Fill the array *)
      lJsonArray.Add(lPerson);
    end;
    (* Add the array to rows property *)
    lJson.Add('rows', lJsonArray);
    Result := lJson.AsJSON;
  finally
    lJson.Free;
  end;
end;
 
function TCGIApp.GetCustomer(AIdPerson: string): string;
var
  lPerson: TJSONObject;
  lJson: TJSONObject;
 
begin
  (* Query the database *)
  FQuery.Close;
  FQuery.Sql.Text := 'select * from clientes where IdCliente=' + AIdPerson;
  FQuery.Open;
  FQuery.First;
 
  lJson := TJSONObject.Create;
  try
    lPerson := TJSONObject.Create;
    fillJsonObject(lPerson);
   (* Add the array to rows property *)
    lJson.Add('success', 'true');
    lJson.Add('data', lPerson);
    Result := lJson.AsJSON;
  finally
    lJson.Free;
  end;
end;
 
function TCGIApp.SavePerson(ARequest: TRequest): string;
var
  lId: string;
  lFirstName: string;
  lLastName: string; 
  lPhone: string;
  lSql: string;
begin
  lId := ARequest.ContentFields.Values['id'];
  lFirstName := ARequest.ContentFields.Values['firstname'];
  lLastName := ARequest.ContentFields.Values['lastname'];
  lPhone := ARequest.ContentFields.Values['phone'];
  if lId <> '' then
    lSql := 'update clientes set ' + 
      'nombres = ''' + lLastName  + ''', ' + 
      'apellido = ''' + lFirstName + ''', ' + 
      'telfijo = ''' + lPhone + ''' where idcliente=' + lId
  else
  begin
 
    lSql := 'insert into clientes(IdCliente, Nombres, Apellido, TelFijo) ' + 
      'values(Gen_Id(SeqClientes, 1),''' + lFirstName + ''', ''' + lLastName + ''', ''' + lPhone + ''')';
  end;
 
  try
    FQuery.Sql.Text := lSql;
    FConn.Transaction.StartTransaction;
    FQuery.ExecSql;
    FConn.Transaction.Commit;
    Result := '{''success'': ''true''}';
  except
  on E: Exception do
    Result := '{''message'': "' + E.message + '"}';
  end;
end;
 
function TCGIApp.DeletePerson(ARequest: TRequest): string;
var
  lId: string;
begin
  lId := ARequest.ContentFields.Values['delete_person'];
  try
    FQuery.Sql.Text := 'delete from clientes where idcliente=' + lId;
    FConn.Transaction.StartTransaction;
    FQuery.ExecSql;
    FConn.Transaction.Commit;
    Result := '{''success'': ''true''}';
  except
  on E: Exception do
    Result := '{''failure'': ''Error deleting person.''}';
  end;
end;
 
Procedure TCGIApp.HandleRequest(ARequest : TRequest; AResponse : TResponse);
var
  lIdPerson: string;
begin
  if ARequest.ContentFields.Values['delete_person'] <> '' then
  begin
    AResponse.Content := DeletePerson(ARequest);
  end
  else
  if ARequest.ContentFields.Values['save_person'] <> '' then
  begin
    AResponse.Content := SavePerson(ARequest);
  end
  else
  begin
    lIdPerson := ARequest.ContentFields.Values['idperson']; 
    if lIdPerson <> '' then
      AResponse.Content := GetCustomer(lIdPerson)
    else
      AResponse.Content := GetCustomersList;
  end;
end;
 
begin
  With TCGIApp.Create(Nil) do
    try
      Initialize;
      ConnectToDatabase;
      Run;
    finally
      Free;
    end;
end.

Скомпилируйте этот файл. Для удобства компиляции, я использую простой bash скрипт. Он выполняет компилирование, а затем копирует результат в директорию /var/www/cgi-bin. Мой Apache 2 сконфигурирован именно на эту директорию. Именно в ней хранятся все мои CGI приложения.

На всякий случай привожу код своего скрипта:

#!/bin/bash
fpc -XX -Xs -b -v ./customerslist.pp
cp ./customerslist /var/www/cgi-bin

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

После публикации первой части заметки многие сказали мне, что для создания простой web-странице приходится проделывать слишком много действий (применяя ObjectPascal). Я согласен с этим замечанием, но хочу заметить, что я не рассматривал использование каких-либо средств упрощающих разработку. Например, вместо того, чтобы создавать код ExtJs в обычном текстовом редакторе как я (хотя, VIM это не совсем обычный текстовый редактор), вы можете воспользоваться визуальным редактором ExtJS Designer (]]>http://www.extjs.com/products/designer/?ref=family]]>). Код на Object Pascal также можно упростить. Для этого просто необходимо воспользоваться фреймворками WebBroker или DataSnap.

Еще одним интересным вопросом от читателей был: «А почему в своем примере я не использовал ExtPascal (]]>http://code.google.com/p/extpascal/]]>)»? Дело в том, что, я не стал применять ExtPascal по причине моего желания продемонстрировать Вам все шаги создания web-приложения, используя лишь ExtJS (html, JS, CGI). Да, применение ExtPascal слишком упрощает разработку, т.к. весь JavaScript код создается автоматически. Но я также считаю, что программисту не помешает знать как работать с чистым ExtJS. Вот именно поэтому я и не ориентировался на применение ExtPascal. Не стоит огорчаться, теперь, когда вы имеете представление о применении ExtJS, вы сможете без труда воспользоваться ExtPascal. В очередной части заметки я обязательно покажу пример работы с ExtPascal.

А что будет дальше?

Перед тем как рассказать о ExtPascal, я покажу Вам, как заменить CGI часть нашгегго примера на DataSnap Server Applicaion. Увидимся!

Автор: Leonardo M. Ramé
WWW: ]]>http://leonardorame.blogspot.com/]]>
Автор перевода: Игорь Антонов aka Spider_NET
Email переводчика:
WWW переводчика: ]]>http://vr-online.ru]]>

ВложениеРазмер
web-samples2-source.rar3.98 КБ