Software Development Kit
to the Delphi-Win32 and Free Pascal compilers
Home > Wiki > PhonebookProject/Article2

PhonebookProject/Article2

english (en) | português (pt)



On this article will be made the implementation of project's logic.

Contents

Business Objects

The application business classes will be declared together, separated by group. Each subfolder within the folder will only have a Core unit with all business classes of that group.

CustomBO

Create a unit (without form) {phonebook}\Source\Core\Custom\CustomBO.pas and place the following contents:

unit CustomBO;

interface

uses
  PressSubject, PressAttributes;

type
  TCustomObject = class(TPressObject)
  end;

  TCustomQuery = class(TPressQuery)
  end;

  TCustomParts = class(TPressParts)
  end;

  TCustomReferences = class(TPressReferences)
  end;

implementation

initialization
  TCustomObject.RegisterClass;
  TCustomQuery.RegisterClass;
  TCustomParts.RegisterAttribute;
  TCustomReferences.RegisterAttribute;

finalization
  TCustomObject.UnregisterClass;
  TCustomQuery.UnregisterClass;
  TCustomParts.UnregisterAttribute;
  TCustomReferences.UnregisterAttribute;

end.

The aim of these classes is to take place between PressObjects and application business classes, in order to add or change features in a general way, without need to change every class.

ContactBO

Create another unit (also without form) {phonebook}\Source\Core\Contato\ContactBO.pas and place the following contents:

unit ContactBO;

interface

uses
  PressSubject, PressAttributes, CustomBO;

type
  {$M+}  // necessary in the FPC
  TContatoFoneParts = class;
  {$M-}  // necessary in the FPC

  TContact = class(TCustomObject)
    _Name: TPressAnsiString;
    _Address: TPressAnsiString;
    _Phones: TContatoFoneParts;
    _City: TPressReference;
  protected
    class function InternalMetadataStr: string; override;
  end;

  TContactQuery = class(TCustomQuery)
    _Name: TPressAnsiString;
    _City: TPressReference;
  protected
    class function InternalMetadataStr: string; override;
  end;

  TPhoneType = (tfFixed, tfMobile, tfFax);

  TContactPhone = class(TCustomObject)
    _PhoneType: TPressEnum;
    _Number: TPressPlainString;
  protected
    class function InternalMetadataStr: string; override;
  end;

  TContactPhoneParts = class(TCustomParts)
  public
    class function ValidObjectClass: TPressObjectClass; override;
  end;

  TCity = class(TCustomObject)
    _Name: TPressAnsiString;
    _State: TPressAnsiString;
  protected
    class function InternalMetadataStr: string; override;
  end;

  TCityQuery = class(TCustomQuery)
    _Name: TPressAnsiString;
  protected
    class function InternalMetadataStr: string; override;
  end;

implementation

initialization
  PressModel.RegisterEnumMetadata(TypeInfo(TPhoneType), 'TPhoneType',
   ['Phone fixed', 'Mobile', 'Fax']);

  TContact.RegisterClass;
  TContactQuery.RegisterClass;
  TContactFone.RegisterClass;
  TContactFoneParts.RegisterAttribute;
  TCity.RegisterClass;
  TCityQuery.RegisterClass;

finalization
  TContact.UnregisterClass;
  TContactQuery.UnregisterClass;
  TContactPhone.UnregisterClass;
  TContactPhoneParts.UnregisterAttribute;
  TCity.UnregisterClass;
  TCityQuery.UnregisterClass;

end.

To implement business classes metadata, position the cursor within class declaration text and press Shift + Ctrl + C. On method created by the IDE, type the following block, to TContact class:

class function TContact.InternalMetadataStr: string;
begin
  Result := 'TContact IsPersistent (' +
   'Name: AnsiString(60);' +
   'Address: AnsiString(80);' +
   'Phones: TContactPhoneParts;' +
   'City: Reference(TCity);' +
   ')';
end;

This is the most important part of the class declaration. The PressObjects will interpret this string and create attributes accordingly. Class members starting with "_" will address these attributes when a object is instantiated.

Now just do the same for the other classes:

begin
  Result := 'TContactQuery(TContact) (' +
   'Name: AnsiString(60);' +
   'City: Reference(TCity);' +
   ')';
end;
begin
  Result := 'TContactPhone IsPersistent (' +
   'PhoneType: Enum(TPhoneType);' +
   'Number: PlainString(15);' +
   ')';
end;
class function TContactPhoneParts.ValidObjectClass: TPressObjectClass;
begin
  Result := TContactPhone;
end;
begin
  Result := 'TCity IsPersistent (' +
   'Name: AnsiString(60);' +
   'State: AnsiString(2);' +
   ')';
end;
begin
  Result := 'TCityQuery(TCity) (' +
   'Name: AnsiString(60);' +
   ')';
end;

MVP

The MVP classes also will be grouped, each subfolder in the folder will only have a Core unit with all its MVP classes.

MainMVP

Create a unit (without form) {phonebook}\Source\Core\Main\MainMVP.pas and place the following contents:

unit MainMVP;

interface

uses
  PressMVPPresenter;

type
  TMainFormPresenter = class(TPressMVPMainFormPresenter)
  protected
    procedure InitPresenter; override;
  end;

implementation

end.

Using the same trick the unit ContactBO, position the text cursor in front of the TMainFormPresenter class declaration, and press Shift + Ctrl + C. Within the statement InitPresenter, implement:

procedure TMainFormPresenter.InitPresenter;
begin
  inherited;
  BindPresenter(TContactEditPresenter, 'NewContactButton');
  BindPresenter(TContactQueryPresenter, 'SearchContactButton');
  BindPresenter(TCityQueryPresenter, 'SearchCityButton');
  BindCommand(TPressMVPCloseApplicationCommand, 'CloseButton');
  PressUserData.Logon('', '');
end;

BindPresenter is a method that makes the connection between a presenter class and a component, and BindCommand method and does the same with a command. When the event OnClick of these components are triggered, The Presenter runs and shows a form, or a command is executed.

The Logon method creates a user with privilege to use all the commands, and will be replaced once the access control is implemented.

These statements require some units:

implementation

uses
  PressUser, PressMVPCommand, ContactMVP;

This Presenter should run on startup of the application, it starts some internal services of PressObjects. Open the main project - Project | View Source (Delphi and Lazarus) - and include the presenter call:

begin
  Application.Initialize;
  Application.CreateForm(TMainForm, MainForm);
  TMainFormPresenter.Run;
end.

The statement TMainFormPresenter.Run is instead of the traditional Application.Run. Save all files in the project.

CustomMVP

Create a unit (without form) {phonebook}\Source\Core\Custom\CustomMVP.pas and place the following contents:

unit CustomMVP;

interface

uses
  PressMVPPresenter;

type
  TCustomEditPresenter = class(TPressMVPFormPresenter)
  protected
    procedure InitPresenter; override;
  end;

  TCustomQueryPresenter = class(TPressMVPQueryPresenter)
  protected
    procedure InitPresenter; override;
  end;

implementation

initialization
  TCustomEditPresenter.RegisterBO(TCustomObject);
  TCustomQueryPresenter.RegisterBO(TCustomQuery);

end.

Using the same trick from previous units, position the text cursor in front of the TCustomEditPresenter class declaration and press Shift + Ctrl + C. Within the statement InitPresenter, implement:

procedure TCustomEditPresenter.InitPresenter;
begin
  inherited;
  BindCommand(TPressMVPSaveObjectCommand, 'SaveButton');
  BindCommand(TPressMVPCancelConfirmObjectCommand, 'CancelButton');
end;

These instructions bind a command to a form's component. Pressing that button, will executed the command, save the object in the database and close the form. Or reverse the changes made to the object in memory, and close the form.

Next class, TCustomQueryPresenter. Position the cursor, press Shift + Ctrl + C and implement:

procedure TCustomQueryPresenter.InitPresenter;
begin
  inherited;
  BindCommand(TPressMVPExecuteQueryCommand, 'SearchButton');
  CreateQueryItemsPresenter('QueryStringGrid');
end;

The first statement connects a default command of Press to search button. The second is a special of Presenters of searchs, and indicates to the MVP framework which is the component that will present the outcome of the query.

These implementations depends of statements made in other units. The uses clause implementation should be this:

uses
  PressMVPCommand, CustomBO;

ContactMVP

Create a unit (without form) {phonebook}\Source\Core\Contato\ContactMVP.pas and place the following contents:

unit ContactMVP;

interface

uses
  CustomMVP;

type
  TContactEditPresenter = class(TCustomEditPresenter)
  protected
    procedure InitPresenter; override;
  end;

  TContactQueryPresenter = class(TCustomQueryPresenter)
  protected
    procedure InitPresenter; override;
    function InternalQueryItemsDisplayNames: string; override;
  end;

  TContactPhoneEditPresenter = class(TCustomEditPresenter)
  protected
    procedure InitPresenter; override;
  end;

  TCityEditPresenter = class(TCustomEditPresenter)
  protected
    procedure InitPresenter; override;
  end;

  TCityQueryPresenter = class(TCustomQueryPresenter)
  protected
    procedure InitPresenter; override;
    function InternalQueryItemsDisplayNames: string; override;
  end;

implementation

uses
  ContactBO;

initialization
  TContactEditPresenter.RegisterBO(TContact);
  TContactQueryPresenter.RegisterBO(TContactQuery);
  TContactPhoneEditPresenter.RegisterBO(TContactPhone);
  TCityEditPresenter.RegisterBO(TCity);
  TCityQueryPresenter.RegisterBO(TCityQuery);

end.

Now, is missing the mapping between classes and business forms. With help of Shift + Ctrl + C, implement the following codes:

procedure TContactEditPresenter.InitPresenter;
begin
  inherited;
  CreateSubPresenter('Name', 'NameEdit');
  CreateSubPresenter('Address', 'AddressEdit');
  CreateSubPresenter('Phones', 'PhonesStringGrid', 'PhoneType;Number');
  CreateSubPresenter('City', 'CityComboBox', 'Name');
end;

The method CreateSubPresenter creates a presenter and their views and models. The parameter indicates respectively the name of the attribute in the business class and the name of the form's component. The third parameter, used in compositions and aggregations, indicates which attribute or attributes of the other class must be presented in control.

procedure TContactQueryPresenter.InitPresenter;
begin
  inherited;
  CreateSubPresenter('Name', 'NameEdit');
  CreateSubPresenter('City', 'CityComboBox', 'Name');
end;
function TContactQueryPresenter.InternalQueryItemsDisplayNames: string;
begin
  Result := 'Name;City.Name';
end;
procedure TContactPhoneEditPresenter.InitPresenter;
begin
  inherited;
  CreateSubPresenter('PhoneType', 'PhoneTypeComboBox');
  CreateSubPresenter('Number', 'NumberEdit');
end;
procedure TCityEditPresenter.InitPresenter;
begin
  inherited;
  CreateSubPresenter('Name', 'NameEdit');
  CreateSubPresenter('State', 'StateEdit');
end;
procedure TCityQueryPresenter.InitPresenter;
begin
  inherited;
  CreateSubPresenter('Name', 'NameEdit');
end;
function TCityQueryPresenter.InternalQueryItemsDisplayNames: string;
begin
  Result := 'Name;State';
end;

Testing the project

Give a Save All and Build All the project to correct the errors of compilation. Run the project, press Shift + Alt + 9 to generate the metadata from the database and open all the forms to test the bindings, but do not try to write anything yet.

Note: forms and controls have the context menu (popup) driven with the right mouse button. This menu holds shortcuts used to include, change, delete and etc. the informations of that component.

If all goes well, go to the next article. In case of errors, review the implementation, names of the forms and units, and the parameters of bindings in the error message.