Added htmx_template_pro sample

This commit is contained in:
Daniele Teti 2024-08-12 10:38:16 +02:00
parent 3429f4a825
commit a452ecd433
52 changed files with 5241 additions and 1498 deletions

View File

@ -0,0 +1,311 @@
unit DAL;
interface
uses
System.JSON,
MVCFramework.SystemJSONUtils,
System.Generics.Collections,
MVCFramework.Serializer.Commons;
type
[MVCNameCase(ncLowerCase)]
TPerson = class
private
FFirstName: string;
FLastName: string;
FAge: Integer;
FItems: string;
FGUID: string;
procedure SetFirstName(const Value: string);
procedure SetLastName(const Value: string);
procedure SetAge(const Value: Integer);
procedure SetGUID(const Value: string);
procedure SetItems(const Value: string);
public
[MVCNameAs('first_name')]
property FirstName: string read FFirstName write SetFirstName;
[MVCNameAs('last_name')]
property LastName: string read FLastName write SetLastName;
property Age: Integer read FAge write SetAge;
property Items: string read FItems write SetItems;
property GUID: string read FGUID write SetGUID;
end;
TPeople = class(TObjectList<TPerson>)
end;
{$M+}
TMyObj = class
private
FRawHTML: String;
procedure SetRawHTML(const Value: String);
published
property RawHTML: String read FRawHTML write SetRawHTML;
end;
{$M-}
TDevice = class
private
fDeviceName: string;
fSelected: Boolean;
public
property DeviceName: string read fDeviceName write fDeviceName;
property Selected: Boolean read fSelected write fSelected;
constructor Create(aDeviceName: string; aSelected: Boolean);
end;
TDeviceList = class(TObjectList<TDevice>)
public
function Contains(const aDeviceName: string): Boolean;
function IndexOf(const aDeviceName: string): Integer;
end;
IPeopleDAL = interface
['{3E534A3E-EAEB-44ED-B74E-EFBBAAAE11B4}']
function GetPeople(const SearchText: String = ''): TPeople;
procedure AddPerson(FirstName, LastName: string; Age: Integer;
Items: TArray<string>);
procedure DeleteByGUID(GUID: string);
function GetPersonByGUID(GUID: string): TPerson;
function GetDevicesList: TDeviceList;
end;
TPeopleDAL = class(TInterfacedObject, IPeopleDAL)
private const
DATAFILE: string = 'people.data';
public
function GetPeople(const SearchText: String = ''): TPeople;
procedure AddPerson(FirstName, LastName: string; Age: Integer;
Items: TArray<string>);
procedure DeleteByGUID(GUID: string);
function GetPersonByGUID(GUID: string): TPerson;
function GetDevicesList: TDeviceList;
end;
TServicesFactory = class sealed
class function GetPeopleDAL: IPeopleDAL;
end;
implementation
uses
System.SyncObjs,
System.IOUtils,
MVCFramework.Serializer.Defaults,
System.SysUtils;
var
// Hey! The storage is a simple json file, so some synchronization is needed
_CS: TCriticalSection = nil;
{ TSimpleDAL }
procedure TPeopleDAL.AddPerson(FirstName, LastName: string; Age: Integer;
Items: TArray<string>);
var
lPeople: TPeople;
lPerson: TPerson;
begin
_CS.Enter;
try
lPeople := GetPeople();
try
lPerson := TPerson.Create;
lPeople.Add(lPerson);
lPerson.FirstName := FirstName;
lPerson.LastName := LastName;
lPerson.Age := Age;
lPerson.Items := string.Join(',', Items);
lPerson.GUID := TGuid.NewGuid.ToString.Replace('{', '').Replace('}', '')
.Replace('-', '');
TFile.WriteAllText(DATAFILE, GetDefaultSerializer.SerializeCollection
(lPeople));
finally
lPeople.Free;
end;
finally
_CS.Leave;
end;
end;
class function TServicesFactory.GetPeopleDAL: IPeopleDAL;
begin
Result := TPeopleDAL.Create;
end;
procedure TPeopleDAL.DeleteByGUID(GUID: string);
var
LJPeople: TPeople;
I: Integer;
begin
_CS.Enter;
try
LJPeople := GetPeople;
try
for I := 0 to LJPeople.Count - 1 do
begin
if LJPeople[I].GUID = GUID then
begin
LJPeople.Delete(I);
break;
end;
end;
TFile.WriteAllText(DATAFILE, GetDefaultSerializer.SerializeCollection
(LJPeople));
finally
LJPeople.Free;
end;
finally
_CS.Leave;
end;
end;
function TPeopleDAL.GetDevicesList: TDeviceList;
begin
Result := TDeviceList.Create(true);
Result.Add(TDevice.Create('smartphone', false));
Result.Add(TDevice.Create('dumbphone', false));
Result.Add(TDevice.Create('laptop', false));
Result.Add(TDevice.Create('desktop', false));
end;
function TPeopleDAL.GetPeople(const SearchText: String): TPeople;
var
LData: string;
lSearch: String;
begin
lSearch := SearchText.ToLower;
_CS.Enter;
try
Result := TPeople.Create;
if TFile.Exists(DATAFILE) then
LData := TFile.ReadAllText(DATAFILE).Trim;
if not LData.IsEmpty then
begin
GetDefaultSerializer.DeserializeCollection(LData, Result, TPerson);
end;
if not SearchText.IsEmpty then
begin
var lToDelete := TPeople.Create(False);
try
for var I := 0 to Result.Count-1 do
begin
if not (
Result[i].FirstName.ToLower.Contains(lSearch) or
Result[i].LastName.ToLower.Contains(lSearch)) then
begin
lToDelete.Add(Result[i]);
end;
end;
for var I := 0 to lToDelete.Count-1 do
begin
Result.Remove(lToDelete[I]);
end;
finally
lToDelete.Free;
end;
end;
finally
_CS.Leave;
end;
end;
function TPeopleDAL.GetPersonByGUID(GUID: string): TPerson;
var
lPeople: TPeople;
lPerson: TPerson;
begin
Result := nil;
lPeople := GetPeople;
try
for lPerson in lPeople do
begin
if lPerson.GUID = GUID then
begin
Result := lPeople.Extract(lPerson);
break;
end;
end;
if not Assigned(Result) then
begin
raise Exception.Create('Person not found');
end;
finally
lPeople.Free;
end;
end;
{ TPerson }
procedure TPerson.SetAge(const Value: Integer);
begin
FAge := Value;
end;
procedure TPerson.SetFirstName(const Value: string);
begin
FFirstName := Value;
end;
procedure TPerson.SetGUID(const Value: string);
begin
FGUID := Value;
end;
procedure TPerson.SetItems(const Value: string);
begin
FItems := Value;
end;
procedure TPerson.SetLastName(const Value: string);
begin
FLastName := Value;
end;
{ TDevice }
constructor TDevice.Create(aDeviceName: string; aSelected: Boolean);
begin
inherited Create;
fDeviceName := aDeviceName;
fSelected := aSelected;
end;
{ TDeviceList }
function TDeviceList.Contains(const aDeviceName: string): Boolean;
begin
Result := IndexOf(aDeviceName) > -1;
end;
function TDeviceList.IndexOf(const aDeviceName: string): Integer;
var
I: Integer;
begin
Result := -1;
for I := 0 to Self.Count - 1 do
begin
if SameText(Self[I].DeviceName, aDeviceName) then
Exit(I);
end;
end;
{ TRawObj }
procedure TMyObj.SetRawHTML(const Value: String);
begin
FRawHTML := Value;
end;
initialization
_CS := TCriticalSection.Create;
finalization
_CS.Free;
end.

View File

@ -1,5 +1,4 @@
object WebModule1: TWebModule1
OldCreateOrder = False
OnCreate = WebModuleCreate
OnDestroy = WebModuleDestroy
Actions = <

View File

@ -0,0 +1,77 @@
unit WebModuleU;
interface
uses System.SysUtils, System.Classes, Web.HTTPApp, MVCFramework;
type
TWebModule1 = class(TWebModule)
procedure WebModuleCreate(Sender: TObject);
procedure WebModuleDestroy(Sender: TObject);
private
FMVCEngine: TMVCEngine;
{ Private declarations }
public
{ Public declarations }
end;
var
WebModuleClass: TComponentClass = TWebModule1;
implementation
uses
MVCFramework.View.Renderers.TemplatePro,
WebSiteControllerU,
System.IOUtils,
MVCFramework.Commons,
MVCFramework.Middleware.Redirect,
MVCFramework.Middleware.StaticFiles;
{ %CLASSGROUP 'Vcl.Controls.TControl' }
{$R *.dfm}
procedure TWebModule1.WebModuleCreate(Sender: TObject);
begin
FMVCEngine := TMVCEngine.Create(Self,
procedure(Config: TMVCConfig)
begin
Config.dotEnv := dotEnv;
// session timeout (0 means session cookie)
Config[TMVCConfigKey.SessionTimeout] := dotEnv.Env('dmvc.session_timeout', '0');
//default content-type
Config[TMVCConfigKey.DefaultContentType] := dotEnv.Env('dmvc.default.content_type', TMVCMediaType.TEXT_HTML);
//default content charset
Config[TMVCConfigKey.DefaultContentCharset] := dotEnv.Env('dmvc.default.content_charset', TMVCConstants.DEFAULT_CONTENT_CHARSET);
//unhandled actions are permitted?
Config[TMVCConfigKey.AllowUnhandledAction] := dotEnv.Env('dmvc.allow_unhandled_actions', 'false');
//enables or not system controllers loading (available only from localhost requests)
Config[TMVCConfigKey.LoadSystemControllers] := dotEnv.Env('dmvc.load_system_controllers', 'true');
//default view file extension
Config[TMVCConfigKey.DefaultViewFileExtension] := dotEnv.Env('dmvc.default.view_file_extension', 'html');
//view path
Config[TMVCConfigKey.ViewPath] := dotEnv.Env('dmvc.view_path', 'templates');
//use cache for server side views (use "false" in debug and "true" in production for faster performances
Config[TMVCConfigKey.ViewCache] := dotEnv.Env('dmvc.view_cache', 'false');
//Max Record Count for automatic Entities CRUD
Config[TMVCConfigKey.MaxEntitiesRecordCount] := dotEnv.Env('dmvc.max_entities_record_count', IntToStr(TMVCConstants.MAX_RECORD_COUNT));
//Enable Server Signature in response
Config[TMVCConfigKey.ExposeServerSignature] := dotEnv.Env('dmvc.expose_server_signature', 'false');
//Enable X-Powered-By Header in response
Config[TMVCConfigKey.ExposeXPoweredBy] := dotEnv.Env('dmvc.expose_x_powered_by', 'true');
// Max request size in bytes
Config[TMVCConfigKey.MaxRequestSize] := dotEnv.Env('dmvc.max_request_size', IntToStr(TMVCConstants.DEFAULT_MAX_REQUEST_SIZE));
end)
.AddController(TWebSiteController)
.AddMiddleware(TMVCRedirectMiddleware.Create(['/'],'/people'))
.SetViewEngine(TMVCTemplateProViewEngine)
end;
procedure TWebModule1.WebModuleDestroy(Sender: TObject);
begin
FMVCEngine.Free;
end;
end.

View File

@ -0,0 +1,209 @@
unit WebSiteControllerU;
interface
uses
MVCFramework, System.Diagnostics, JsonDataObjects, MVCFramework.Commons, MVCFramework.HTMX;
type
[MVCPath('/people')]
TWebSiteController = class(TMVCController)
protected
function GeneratePeopleListAsCSV: String;
public
[MVCPath]
[MVCHTTPMethods([httpGET])]
function PeopleSearch(const [MVCFromQueryString('q', '')] SearchText: String): String;
[MVCPath('/exports/csv')]
[MVCHTTPMethods([httpGET])]
function ExportPeopleListAsCSV_API: String;
[MVCPath]
[MVCHTTPMethods([httpPOST])]
[MVCConsumes(TMVCMediaType.APPLICATION_FORM_URLENCODED)]
procedure SavePerson(
const [MVCFromContentField('first_name')] FirstName: String;
const [MVCFromContentField('last_name')] LastName: String;
const [MVCFromContentField('age', 0)] Age: Integer;
const [MVCFromContentField('items')] Devices: TArray<String>
);
[MVCPath('/delete/($guid)')]
[MVCHTTPMethods([httpDELETE])]
procedure DeletePerson(const guid: string);
[MVCPath('/new')]
[MVCHTTPMethods([httpGET])]
[MVCProduces(TMVCMediaType.TEXT_HTML)]
function NewPerson: String;
[MVCPath('/modal/fordelete/($guid)')]
[MVCHTTPMethods([httpGET])]
[MVCProduces(TMVCMediaType.TEXT_HTML)]
function ShowModalForDelete(guid: string): String;
[MVCPath('/edit/($guid)')]
[MVCHTTPMethods([httpGET])]
[MVCProduces(TMVCMediaType.TEXT_HTML)]
function EditPerson(guid: string): String;
[MVCPath]
[MVCHTTPMethods([httpGET])]
[MVCProduces(TMVCMediaType.TEXT_HTML)]
procedure Index;
[MVCPath('/modal')]
[MVCHTTPMethods([httpGET])]
[MVCProduces(TMVCMediaType.TEXT_HTML)]
function ShowModal: String;
end;
implementation
{ TWebSiteController }
uses DAL, System.SysUtils, Web.HTTPApp;
procedure TWebSiteController.DeletePerson(const guid: string);
var
LDAL: IPeopleDAL;
begin
LDAL := TServicesFactory.GetPeopleDAL;
LDAL.DeleteByGUID(GUID);
Context.Response.HXSetLocation('/people');
RenderStatusMessage(HTTP_STATUS.OK);
end;
function TWebSiteController.EditPerson(guid: string): String;
var
LDAL: IPeopleDAL;
lPerson: TPerson;
lDevices: TDeviceList;
lItem: TDevice;
begin
LDAL := TServicesFactory.GetPeopleDAL;
lPerson := LDAL.GetPersonByGUID(guid);
try
lDevices := LDAL.GetDevicesList;
try
ViewData['person'] := lPerson;
for lItem in lDevices do
begin
lItem.Selected := lPerson.Items.Contains(lItem.DeviceName);
end;
ViewData['deviceslist'] := lDevices;
Result := Page(['editperson']);
finally
lDevices.Free;
end;
finally
lPerson.Free;
end;
end;
function TWebSiteController.ExportPeopleListAsCSV_API: String;
begin
ContentType := TMVCMediaType.TEXT_CSV;
Context.Response.CustomHeaders.Values['Content-Disposition'] := 'attachment; filename=people.csv';
Result := GeneratePeopleListAsCSV;
end;
function TWebSiteController.GeneratePeopleListAsCSV: String;
var
LDAL: IPeopleDAL;
lPeople: TPeople;
begin
LDAL := TServicesFactory.GetPeopleDAL;
lPeople := LDAL.GetPeople;
try
ViewData['people'] := lPeople;
Result := Page(['people_list.csv']);
finally
lPeople.Free;
end;
end;
procedure TWebSiteController.Index;
begin
Redirect('/people');
end;
function TWebSiteController.NewPerson: String;
var
LDAL: IPeopleDAL;
lDevices: TDeviceList;
begin
LDAL := TServicesFactory.GetPeopleDAL;
lDevices := LDAL.GetDevicesList;
try
ViewData['deviceslist'] := lDevices;
ViewData['ishtmx'] := Context.Request.IsHTMX;
Result := Page(['editperson']);
finally
lDevices.Free;
end;
end;
function TWebSiteController.PeopleSearch(const SearchText: String): String;
var
LDAL: IPeopleDAL;
lPeople: TPeople;
begin
LDAL := TServicesFactory.GetPeopleDAL;
lPeople := LDAL.GetPeople(SearchText);
try
ViewData['people'] := lPeople;
ViewData['ishtmx'] := Context.Request.IsHTMX;
if Context.Request.IsHTMX then
begin
if SearchText.IsEmpty then
Context.Response.HXSetPushUrl('/people')
else
Context.Response.HXSetPushUrl('/people?q=' + SearchText);
end;
ViewData['q'] := SearchText;
Result := PageFragment(['people_list']);
finally
lPeople.Free;
end;
end;
procedure TWebSiteController.SavePerson(
const FirstName: String;
const LastName: String;
const Age: Integer;
const Devices: TArray<String>);
var
LPeopleDAL: IPeopleDAL;
begin
if FirstName.IsEmpty or LastName.IsEmpty or (Age <= 0) then
begin
{ TODO -oDaniele -cGeneral : Show how to properly render an exception }
raise EMVCException.Create('Invalid data', 'First name, last name and age are not optional', 0);
end;
LPeopleDAL := TServicesFactory.GetPeopleDAL;
LPeopleDAL.AddPerson(FirstName, LastName, Age, Devices);
Context.Response.HXSetRedirect('/people');
end;
function TWebSiteController.ShowModal: String;
begin
ViewData['message'] := 'Do you really want to delete row?';
ViewData['title'] := 'Bootstrap Modal Dialog';
Result := Page(['modal']);
end;
function TWebSiteController.ShowModalForDelete(guid: string): String;
begin
ViewData['title'] := 'Bootstrap Modal Dialog';
ViewData['message'] := 'Do you really want to delete row?';
ViewData['guid'] := guid;
Result := Page(['modal']);
end;
end.

View File

@ -0,0 +1 @@
[{"first_name":"Bruce","last_name":"Banner","age":56,"items":"smartphone,dumbphone","guid":"2290EE213DFB4855894A3FC91FE52C17"},{"first_name":"Reed","last_name":"Richards","age":45,"items":"laptop,smartphone","guid":"298CE047B4C24D67B29710BF4ABE290C"},{"first_name":"Scott","last_name":"Summers","age":54,"items":"desktop","guid":"3DACB879E83749EDA68389EBA2286A13"},{"first_name":"Daniele","last_name":"Teti","age":40,"items":"dumbphone,laptop","guid":"C2C002A595694C7CBD3CA1F3123F0EEB"},{"first_name":"Bruce","last_name":"Banner","age":56,"items":"desktop","guid":"97D40FD94580407FA60B9A09EAE2DA8E"},{"first_name":"Bruce","last_name":"Banner","age":56,"items":"smartphone,dumbphone,laptop","guid":"F7EAC68F3709415985C699653950C85B"}]

View File

@ -0,0 +1,8 @@
<a
hx-get="/people/modal/fordelete/{{:people.guid}}"
href=""
hx-target="#modals-here"
hx-trigger="click"
data-bs-toggle="modal"
data-bs-target="#modals-here"
>Delete</a>

View File

@ -0,0 +1,78 @@
{{if(!ishtmx)}}
{{include("partials/header.html")}}
{{endif}}
<form class="form form-horizontal" id="myForm" name="myForm" hx-post="/people">
<input type="hidden" value="{{:person.guid}}" name="guid">
<div class="row">
<div class="col">
{{if(!person)}}
<h3>New Person</h3>
{{else}}
<h3>Edit Person</h3>
{{endif}}
</div>
</div>
<div class="row">
<div class="col">
<div class="form-group">
<label for="first_name" class="control-label">First name</label>
<input type="text" value="{{:person.firstname}}" class="form-control" id="first_name" placeholder="First name" name="first_name" autocomplete="first_name" >
</div>
</div>
<div class="col">
<div class="form-group">
<label for="last_name" class="control-label">Last name</label>
<input type="text" value="{{:person.lastname}}" class="form-control" id="last_name" placeholder="Last name" name="last_name">
</div>
</div>
</div>
<div class="row">
<div class="col">
<div class="form-group">
<label for="age" class="control-label">Age</label>
<input type="number" value="{{:person.age}}" class="form-control" id="age" placeholder="Age" name="age">
</div>
</div>
<div class="col">
<div class="form-group">
<label for="items" class="col-sm-10 control-label">Devices</label>
<select id="items" name="items" multiple class="form-control">
{{loop(deviceslist)}}
<option value="{{:deviceslist.devicename}}" {{if(deviceslist.selected)}}selected{{endif}}>{{:deviceslist.devicename}}</option>
{{endloop}}
</select>
<span style="font-size: 80%">(Ctrl+Click to select multiple devices)</span>
</div>
</div>
</div>
<div class="row" style="padding-top:2rem">
<div class="col">
<button type="button" class="btn btn-secondary btn-block w-100" onclick="history.back()">Return to the list</button>
</div>
<div class="col offset-4">
</div>
<div class="col">
<button type="submit" class="btn btn-primary btn-block w-100">Save</button>
</div>
<div class="col">
{{if(person)}}
<button
type="button"
hx-confirm="Are you sure you wish to delete user?"
hx-delete="/people/delete/{{:person.guid}}"
class="btn btn-danger btn-block w-100">
Delete
</button>
{{endif}}
</div>
</div>
</form>
{{if(!ishtmx)}}
{{include("partials/footer.html")}}
{{endif}}

View File

@ -0,0 +1,14 @@
<div class="modal-dialog modal-dialog-centered">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">{{:title}}</h5>
</div>
<div class="modal-body">
<p>{{:message}}</p>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-primary" data-bs-dismiss="modal" hx-delete="/people/delete/{{:guid}}">Yes, delete it!</button>
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">No</button>
</div>
</div>
</div>

View File

@ -0,0 +1,17 @@
<div class="row_fluid">
<div class="col-sm">
<div style="height: 100px"></div>
</div>
</div>
<div class="row">
<div class="col-sm bg-primary">
<span style="color: white">Powered by DMVCFramework</span>
</div>
<div class="col-sm bg-warning" style="text-align: right">
<span><a target="_blank" href="https://github.com/danieleteti/templatepro">Template Pro</a> project site</span>
</div>
</div>
</div>
</body>
</html>

View File

@ -0,0 +1,19 @@
<!DOCTYPE html>
<html>
<header>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-EVSTQN3/azprG1Anm3QDgpJLIm9Nao0Yz1ztcQTwFspd3yD65VohhpuuCOmLASjC" crossorigin="anonymous">
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/js/bootstrap.bundle.min.js" integrity="sha384-MrcW6ZMFYlzcLA8Nl+NtUVF0sA7MsXsP1UyJoMp4YLEuNSfAP+JcXn/tWtIaxVXM" crossorigin="anonymous"></script>
<script src="https://unpkg.com/htmx.org@2.0.0"></script>
<style>
h1 a:hover {
text-decoration: none;
}
</style>
</header>
<body hx-boost="true" class="text-capitalize">
<div id="main" class="container">
<div class="shadow-sm p-3 mb-5 bg-body-tertiary rounded">
<h1><a href="https://htmx.org/" target="_blank">HTMX</a> Sample</h1>
</div>

View File

@ -0,0 +1,9 @@
<div id="modals-here"
class="modal modal-blur fade"
style="display: none"
aria-hidden="false"
tabindex="-1">
<div class="modal-dialog modal-lg modal-dialog-centered" role="document">
<div class="modal-content"></div>
</div>
</div>

View File

@ -0,0 +1 @@
{{first_name}}, {{last_name}}

View File

@ -0,0 +1,4 @@
guid;first_name;last_name;age
{{loop(people)}}
{{:people.guid}};"{{:people.firstname}}";"{{:people.lastname}}";{{:people.age}}
{{endloop}}

View File

@ -0,0 +1,13 @@
{{if(!ishtmx)}}
{{include("partials/header.html")}}
{{include("people_list_search.html")}}
{{endif}}
<div class="row" id="people_list">
<div class="col">
{{include("people_table.html")}}
</div>
</div>
{{if(!ishtmx)}}
{{include("people_list_bottom.html")}}
{{include("partials/footer.html")}}
{{endif}}

View File

@ -0,0 +1,10 @@
<div class="row padding mx-auto">
<div class="col-2 text-left">
<a href="/people/exports/csv" class="btn btn-dark" download="people.csv" target="_blank">Export as CSV</a>
</div>
<div class="offset-8 col-2 text-right">
<button class="btn btn-primary" hx-on:click="window.location.href = '/people/new'">Add New Person</button>
</div>
</div>
{{include("partials/modal_placeholder.html")}}

View File

@ -0,0 +1,13 @@
<div class="row_fluid">
<div class="offset-sm-6 col-sm-6 text-right">
<form hx-get="/people" hx-target="#people_list">
<div class="input-group mb-3">
<input name="q" type="text" class="form-control" placeholder="Contains..." value="{{:q}}">
<div class="input-group-append">
<button class="btn btn-outline-secondary" type="submit">Search</button>
</div>
</div>
</form>
</div>
</div>

View File

@ -0,0 +1,28 @@
<table class="table table-striped">
<thead>
<tr>
<th scope="col">#</th>
<th scope="col">First name</th>
<th scope="col">Last name</th>
<th scope="col">Age</th>
<th scope="col">&nbsp;</th>
</tr>
</thead>
<tbody>
{{if(!people)}}
<tr>
<td class="text-center" colspan="5">&lt;&lt;No People Found&gt;&gt;</td>
</tr>
{{else}}
{{loop(people)}}
<tr>
<td>{{:people.@@index}}</td>
<td>{{:people.FirstName}}</td>
<td>{{:people.LastName}}</td>
<td>{{:people.Age}}</td>
<td class="text-right">{{include("delete_person_link.html")}} | {{include("view_person_link.html")}}</td>
</tr>
{{endloop}}
{{endif}}
</tbody>
</table>

View File

@ -0,0 +1,85 @@
<!DOCTYPE html>
<html>
<header>
<style>
body {
font-family: Consolas, 'Courier New';
}
blockquote {
font-style: italic;
color: #a0a0a0;
padding: 0.2em;
}
.section {
background-color: #3a3a3a;
color: white;
border-left: 0.5em red solid;
padding: 0.5em;
}
.box {
border: thin black solid;
margin: auto;
width: 80%;
padding: 2em;
}
</style>
</header>
<body>
<h1>Mustache Template Showcase <small><a href="/people">return to the app</a></small></h1>
<p>
This page is a showcase for all the mustache features usable from DMVCFramework Server Side Views using the default Mustache engine.
</p>
<div>
<h2 class="section">List of objects</h2>
<div>
{{^people}}
<div>No People Found</div>
{{/people}}
{{#people}}
<div>{{-index}}. {{first_name}} {{last_name}}</div>
{{/people}}
</div>
</div>
<div>
<h2 class="section">Handle empty list of objects</h2>
<div>
{{^people2}}
<div>No People Found</div>
{{/people2}}
{{#people2}}
<div>{{-index}}. {{first_name}} {{last_name}}</div>
{{/people2}}
</div>
</div>
<div>
<h2 class="section">Avoid HTML automatic escaping using {{=<% %>=}} {{{ content }}</h2>
<%={{ }}=%>
<div class="box">
{{#myobj}}
{{{rawhtml}}
{{/myobj}}
<br>
<blockquote >Check source code to know how to escape curly braces</blockquote >
</div>
</div>
<div>
<h2 class="section">Handling partials | <small>This partial is located in <code>partials/partial_person.mustache</code></small></h2>
<div>
<ul>
{{#people}}
<li>{{>partials/partial_person}}</li>
{{/people}}
</ul>
</div>
</div>
</body>
</html>

View File

@ -0,0 +1 @@
<a href="/people/edit/{{:people.guid}}">View</a>

View File

@ -0,0 +1,75 @@
program htmx_templatepro;
{$APPTYPE CONSOLE}
uses
System.SysUtils,
MVCFramework,
MVCFramework.Signal,
MVCFramework.Logger,
MVCFramework.Console,
{$IFDEF MSWINDOWS}
Winapi.ShellAPI,
Winapi.Windows,
{$ENDIF }
IdHTTPWebBrokerBridge,
Web.WebReq,
Web.WebBroker,
WebModuleU in 'WebModuleU.pas' {WebModule1: TWebModule},
WebSiteControllerU in 'WebSiteControllerU.pas',
DAL in 'DAL.pas',
MyDataModuleU in '..\renders\MyDataModuleU.pas' {MyDataModule: TDataModule},
MVCFramework.Commons,
MVCFramework.DotEnv;
{$R *.res}
procedure RunServer(APort: Integer);
var
LServer: TIdHTTPWebBrokerBridge;
begin
ReportMemoryLeaksOnShutdown := True;
LogI('HTMX DMVCFramework Sample');
LogI(Format('Starting HTTP Server on port %d', [APort]));
ResetConsole;
LServer := TIdHTTPWebBrokerBridge.Create(nil);
try
LServer.DefaultPort := APort;
LServer.Active := True;
{$IFDEF MSWINDOWS}
//ShellExecute(0, 'open', 'http://localhost:8080', nil, nil, SW_SHOW);
{$ENDIF}
LogI('HTMX DMVCFramework Sample');
LogI('Ctrl+C to stop the server');
WaitForTerminationSignal;
EnterInShutdownState;
ResetConsole;
LServer.Active := False;
finally
LServer.Free;
end;
end;
begin
{ Enable ReportMemoryLeaksOnShutdown during debug }
// ReportMemoryLeaksOnShutdown := True;
IsMultiThread := True;
UseConsoleLogger := True;
// DMVCFramework Specific Configuration
// When MVCSerializeNulls = True empty nullables and nil are serialized as json null.
// When MVCSerializeNulls = False empty nullables and nil are not serialized at all.
MVCSerializeNulls := True;
try
if WebRequestHandler <> nil then
WebRequestHandler.WebModuleClass := WebModuleClass;
WebRequestHandlerProc.MaxConnections := dotEnv.Env('dmvc.handler.max_connections', 1024);
RunServer(dotEnv.Env('dmvc.server.port', 8080));
except
on E: Exception do
LogE(E.ClassName + ': ' + E.Message);
end;
end.

View File

@ -1,15 +1,15 @@
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<ProjectGuid>{26F03627-A96B-4F16-8E35-D0D1890F6748}</ProjectGuid>
<ProjectGuid>{563921D8-AB80-4E14-AD4E-36870C7E2008}</ProjectGuid>
<ProjectVersion>20.1</ProjectVersion>
<FrameworkType>VCL</FrameworkType>
<MainSource>ServerSideViewsCustomEngine.dpr</MainSource>
<MainSource>htmx_templatepro.dpr</MainSource>
<Base>True</Base>
<Config Condition="'$(Config)'==''">Debug</Config>
<Platform Condition="'$(Platform)'==''">Win32</Platform>
<TargetedPlatforms>1</TargetedPlatforms>
<AppType>Console</AppType>
<ProjectName Condition="'$(ProjectName)'==''">ServerSideViewsCustomEngine</ProjectName>
<ProjectName Condition="'$(ProjectName)'==''">htmx_templatepro</ProjectName>
</PropertyGroup>
<PropertyGroup Condition="'$(Config)'=='Base' or '$(Base)'!=''">
<Base>true</Base>
@ -44,7 +44,7 @@
<Icns_MainIcns>$(BDS)\bin\delphi_PROJECTICNS.icns</Icns_MainIcns>
<Icon_MainIcon>$(BDS)\bin\delphi_PROJECTICON.ico</Icon_MainIcon>
<DCC_Namespace>System;Xml;Data;Datasnap;Web;Soap;Vcl;Vcl.Imaging;Vcl.Touch;Vcl.Samples;Vcl.Shell;$(DCC_Namespace)</DCC_Namespace>
<SanitizedProjectName>ServerSideViewsCustomEngine</SanitizedProjectName>
<SanitizedProjectName>htmx_templatepro</SanitizedProjectName>
<DCC_DcuOutput>.\$(Platform)\$(Config)</DCC_DcuOutput>
<DCC_ExeOutput>.\$(Platform)\$(Config)</DCC_ExeOutput>
<DCC_E>false</DCC_E>
@ -57,7 +57,7 @@
<DCC_Namespace>Winapi;System.Win;Data.Win;Datasnap.Win;Web.Win;Soap.Win;Xml.Win;Bde;$(DCC_Namespace)</DCC_Namespace>
<VerInfo_Locale>1033</VerInfo_Locale>
<DCC_ExeOutput>.\bin</DCC_ExeOutput>
<VerInfo_Keys>CompanyName=;FileVersion=1.0.0.0;InternalName=;LegalCopyright=;LegalTrademarks=;OriginalFilename=;ProductVersion=1.0.0.0;Comments=;ProgramID=com.embarcadero.$(MSBuildProjectName);FileDescription=$(MSBuildProjectName);ProductName=$(MSBuildProjectName)</VerInfo_Keys>
<VerInfo_Keys>CompanyName=;FileVersion=1.0.0.0;InternalName=;LegalCopyright=;LegalTrademarks=;OriginalFilename=;ProductVersion=1.0.0.0;Comments=;ProgramID=com.embarcadero.$(ModuleName);FileDescription=$(ModuleName);ProductName=$(ModuleName)</VerInfo_Keys>
<Manifest_File>None</Manifest_File>
<DCC_UsePackage>DBXSqliteDriver;RESTComponents;DataSnapServerMidas;DBXDb2Driver;DBXInterBaseDriver;vclactnband;frxe23;vclFireDAC;emsclientfiredac;DataSnapFireDAC;svnui;tethering;FireDACADSDriver;DBXMSSQLDriver;DatasnapConnectorsFreePascal;FireDACMSSQLDriver;vcltouch;vcldb;bindcompfmx;svn;Intraweb;DBXOracleDriver;ipstudiowinwordxp;inetdb;FmxTeeUI;FireDACIBDriver;fmx;fmxdae;DelphiCookbookListViewAppearance;vclib;FireDACDBXDriver;dbexpress;IndyCore;vclx;dsnap;DataSnapCommon;emsclient;FireDACCommon;bdertl;RESTBackendComponents;DataSnapConnectors;VCLRESTComponents;soapserver;frxTee23;JclDeveloperTools;vclie;CPortLibDXE;bindengine;DBXMySQLDriver;FireDACOracleDriver;CloudService;FireDACMySQLDriver;DBXFirebirdDriver;FireDACCommonDriver;DataSnapClient;inet;bindcompdbx;IndyIPCommon;vcl;DBXSybaseASEDriver;IndyIPServer;IndySystem;FireDACDb2Driver;dsnapcon;FireDACMSAccDriver;fmxFireDAC;FireDACInfxDriver;vclimg;TeeDB;FireDAC;Jcl;FireDACSqliteDriver;FireDACPgDriver;ibmonitor;FireDACASADriver;DBXOdbcDriver;FireDACTDataDriver;FMXTee;ipstudiowinclient;soaprtl;DbxCommonDriver;ibxpress;Tee;DataSnapServer;xmlrtl;soapmidas;DataSnapNativeClient;fmxobj;vclwinx;ibxbindings;rtl;FireDACDSDriver;DbxClientDriver;DTKANPRPackage;DBXSybaseASADriver;CustomIPTransport;vcldsnap;CodeSiteExpressPkg;SampleListViewMultiDetailAppearancePackage;bindcomp;appanalytics;ipstudiowin;DBXInformixDriver;officeXPrt;IndyIPClient;bindcompvcl;frxDB23;vcldbx;TeeUI;vclribbon;dbxcds;VclSmp;adortl;FireDACODBCDriver;JclVcl;DataSnapIndy10ServerTransport;dsnapxml;DataSnapProviderClient;dbrtl;inetdbxpress;FireDACMongoDBDriver;IndyProtocols;frx23;JclContainers;fmxase;$(DCC_UsePackage)</DCC_UsePackage>
</PropertyGroup>
@ -76,6 +76,7 @@
<VerInfo_Locale>1033</VerInfo_Locale>
<DCC_RemoteDebug>false</DCC_RemoteDebug>
<VerInfo_Keys>CompanyName=;FileVersion=1.0.0.0;InternalName=;LegalCopyright=;LegalTrademarks=;OriginalFilename=;ProductVersion=1.0.0.0;Comments=;ProgramID=com.embarcadero.$(MSBuildProjectName);FileDescription=$(MSBuildProjectName);ProductName=$(MSBuildProjectName)</VerInfo_Keys>
<AppDPIAwarenessMode>none</AppDPIAwarenessMode>
</PropertyGroup>
<PropertyGroup Condition="'$(Cfg_2)'!=''">
<DCC_LocalDebugSymbols>false</DCC_LocalDebugSymbols>
@ -92,15 +93,11 @@
<DesignClass>TWebModule</DesignClass>
</DCCReference>
<DCCReference Include="WebSiteControllerU.pas"/>
<DCCReference Include="MVCFramework.View.Renderers.TemplatePro.pas"/>
<DCCReference Include="lib\TemplateProU.pas"/>
<DCCReference Include="DAL.pas"/>
<DCCReference Include="..\renders\MyDataModuleU.pas">
<Form>MyDataModule</Form>
<FormType>dfm</FormType>
<DesignClass>TDataModule</DesignClass>
</DCCReference>
<DCCReference Include="..\..\sources\MVCFramework.Cache.pas"/>
<DCCReference Include="..\..\sources\MVCFramework.Serializer.HTML.pas"/>
<BuildConfiguration Include="Base">
<Key>Base</Key>
</BuildConfiguration>
@ -119,15 +116,23 @@
<BorlandProject>
<Delphi.Personality>
<Source>
<Source Name="MainSource">ServerSideViewsCustomEngine.dpr</Source>
<Source Name="MainSource">htmx_templatepro.dpr</Source>
</Source>
<Excluded_Packages>
<Excluded_Packages Name="$(BDSBIN)\dcloffice2k250.bpl">Microsoft Office 2000 Sample Automation Server Wrapper Components</Excluded_Packages>
<Excluded_Packages Name="$(BDSBIN)\dclofficexp250.bpl">Microsoft Office XP Sample Automation Server Wrapper Components</Excluded_Packages>
<Excluded_Packages Name="$(BDSBIN)\dcloffice2k290.bpl">Microsoft Office 2000 Sample Automation Server Wrapper Components</Excluded_Packages>
<Excluded_Packages Name="$(BDSBIN)\dclofficexp290.bpl">Microsoft Office XP Sample Automation Server Wrapper Components</Excluded_Packages>
<Excluded_Packages Name="$(BDSBIN)\bcboffice2k290.bpl">Embarcadero C++Builder Office 2000 Servers Package</Excluded_Packages>
<Excluded_Packages Name="$(BDSBIN)\bcbofficexp290.bpl">Embarcadero C++Builder Office XP Servers Package</Excluded_Packages>
</Excluded_Packages>
</Delphi.Personality>
<Deployment Version="4">
<DeployFile LocalName="bin\ServerSideViewsCustomEngine.exe" Configuration="Debug" Class="ProjectOutput"/>
<DeployFile LocalName="bin\ServerSideViews.exe" Configuration="Debug" Class="ProjectOutput"/>
<DeployFile LocalName="bin\htmx_templatepro.exe" Configuration="Debug" Class="ProjectOutput">
<Platform Name="Win32">
<RemoteName>htmx_templatepro.exe</RemoteName>
<Overwrite>true</Overwrite>
</Platform>
</DeployFile>
<DeployClass Name="AdditionalDebugSymbols">
<Platform Name="OSX32">
<Operation>1</Operation>

View File

@ -15,10 +15,10 @@ type
public
[MVCPath('/public')]
[MVCHTTPMethod([httpGET])]
procedure PublicSection(ctx: TWebContext);
procedure PublicSection;
[MVCPath('/')]
[MVCHTTPMethod([httpGET])]
procedure Index(ctx: TWebContext);
procedure Index;
end;
[MVCPath('/admin')]
@ -30,7 +30,7 @@ type
[MVCPath('/role1')]
[MVCProduces('text/html')]
[MVCHTTPMethod([httpGET])]
procedure OnlyRole1(ctx: TWebContext);
procedure OnlyRole1;
[MVCPath('/role1')]
[MVCProduces('application/json')]
[MVCHTTPMethod([httpGET])]
@ -38,7 +38,7 @@ type
[MVCPath('/role2')]
[MVCProduces('text/html')]
[MVCHTTPMethod([httpGET])]
procedure OnlyRole2(ctx: TWebContext);
procedure OnlyRole2;
end;
implementation
@ -48,12 +48,12 @@ uses
{ TApp1MainController }
procedure TApp1MainController.Index(ctx: TWebContext);
procedure TApp1MainController.Index;
begin
Redirect('/index.html');
end;
procedure TApp1MainController.PublicSection(ctx: TWebContext);
procedure TApp1MainController.PublicSection;
begin
Render('This is a public section');
end;
@ -69,12 +69,12 @@ begin
AHandled := False;
end;
procedure TAdminController.OnlyRole1(ctx: TWebContext);
procedure TAdminController.OnlyRole1;
var
lPair: TPair<String, String>;
begin
ContentType := TMVCMediaType.TEXT_PLAIN;
ResponseStream.AppendLine('Hey! Hello ' + ctx.LoggedUser.UserName +
ResponseStream.AppendLine('Hey! Hello ' + Context.LoggedUser.UserName +
', now you are a logged user and this is a protected content!');
ResponseStream.AppendLine('As logged user you have the following roles: ' +
sLineBreak + string.Join(sLineBreak, Context.LoggedUser.Roles.ToArray));
@ -119,10 +119,10 @@ begin
Render(lJObj);
end;
procedure TAdminController.OnlyRole2(ctx: TWebContext);
procedure TAdminController.OnlyRole2;
begin
ContentType := TMVCMediaType.TEXT_PLAIN;
ResponseStream.AppendLine('Hey! Hello ' + ctx.LoggedUser.UserName +
ResponseStream.AppendLine('Hey! Hello ' + Context.LoggedUser.UserName +
', now you are a logged user and this is a protected content!');
ResponseStream.AppendLine('As logged user you have the following roles: ' +
sLineBreak + string.Join(sLineBreak, Context.LoggedUser.Roles.ToArray));

View File

@ -64,7 +64,7 @@ begin
Log(Context.Request.Headers['User-Agent']);
if Context.Request.Headers['User-Agent'].Contains('Android') then
begin
Context.Response.Location := 'http://play.google.com';
Context.Response.Location := 'https://play.google.com';
Context.Response.StatusCode := HTTP_STATUS.TemporaryRedirect; // 307 - temporary redirect
Handled := True;
end;

View File

@ -14,9 +14,14 @@ type
[MVCPath]
[MVCHTTPMethod([httpGET])]
procedure Index;
[MVCPath('/profilersample1')]
[MVCHTTPMethod([httpGET])]
procedure ProfilerSample1;
[MVCPath('/profilersample2')]
[MVCHTTPMethod([httpGET])]
procedure ProfilerSample2;
protected
fCalls: Integer;
procedure ProcA;
@ -46,6 +51,13 @@ begin
NotProfiled(); //this line is not profiled
end;
procedure TMyController.ProfilerSample2;
begin
var lProf := Profiler.Start(Context.ActionQualifiedName);
Sleep(100);
Render('Hello World');
end;
procedure TMyController.DoSomething;
begin
begin var lProf := Profiler.Start('DoSomething');

View File

@ -13,7 +13,8 @@ uses
IdContext,
IdHTTPWebBrokerBridge,
MainControllerU in 'MainControllerU.pas',
WebModuleU in 'WebModuleU.pas', MVCFramework.Logger {MyWebModule: TWebModule};
WebModuleU in 'WebModuleU.pas',
MVCFramework.Logger {MyWebModule: TWebModule};
{$R *.res}

View File

@ -1,7 +1,7 @@
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<ProjectGuid>{9A719B2E-ABBC-406A-9B25-20444D1F53B3}</ProjectGuid>
<ProjectVersion>19.5</ProjectVersion>
<ProjectVersion>20.1</ProjectVersion>
<FrameworkType>None</FrameworkType>
<Base>True</Base>
<Config Condition="'$(Config)'==''">Debug</Config>
@ -9,6 +9,7 @@
<TargetedPlatforms>1</TargetedPlatforms>
<AppType>Console</AppType>
<MainSource>ProfilingSample.dpr</MainSource>
<ProjectName Condition="'$(ProjectName)'==''">ProfilingSample</ProjectName>
</PropertyGroup>
<PropertyGroup Condition="'$(Config)'=='Base' or '$(Base)'!=''">
<Base>true</Base>
@ -23,11 +24,31 @@
<CfgParent>Base</CfgParent>
<Base>true</Base>
</PropertyGroup>
<PropertyGroup Condition="('$(Platform)'=='iOSDevice64' and '$(Base)'=='true') or '$(Base_iOSDevice64)'!=''">
<Base_iOSDevice64>true</Base_iOSDevice64>
<CfgParent>Base</CfgParent>
<Base>true</Base>
</PropertyGroup>
<PropertyGroup Condition="('$(Platform)'=='iOSSimARM64' and '$(Base)'=='true') or '$(Base_iOSSimARM64)'!=''">
<Base_iOSSimARM64>true</Base_iOSSimARM64>
<CfgParent>Base</CfgParent>
<Base>true</Base>
</PropertyGroup>
<PropertyGroup Condition="('$(Platform)'=='Linux64' and '$(Base)'=='true') or '$(Base_Linux64)'!=''">
<Base_Linux64>true</Base_Linux64>
<CfgParent>Base</CfgParent>
<Base>true</Base>
</PropertyGroup>
<PropertyGroup Condition="('$(Platform)'=='OSX64' and '$(Base)'=='true') or '$(Base_OSX64)'!=''">
<Base_OSX64>true</Base_OSX64>
<CfgParent>Base</CfgParent>
<Base>true</Base>
</PropertyGroup>
<PropertyGroup Condition="('$(Platform)'=='OSXARM64' and '$(Base)'=='true') or '$(Base_OSXARM64)'!=''">
<Base_OSXARM64>true</Base_OSXARM64>
<CfgParent>Base</CfgParent>
<Base>true</Base>
</PropertyGroup>
<PropertyGroup Condition="('$(Platform)'=='Win32' and '$(Base)'=='true') or '$(Base_Win32)'!=''">
<Base_Win32>true</Base_Win32>
<CfgParent>Base</CfgParent>
@ -80,9 +101,29 @@
<DCC_UsePackage>fmx;DbxCommonDriver;bindengine;IndyIPCommon;emsclient;FireDACCommonDriver;IndyProtocols;IndyIPClient;dbxcds;FmxTeeUI;bindcompfmx;ibmonitor;FireDACSqliteDriver;DbxClientDriver;soapmidas;fmxFireDAC;dbexpress;Python;inet;DataSnapCommon;dbrtl;FireDACDBXDriver;CustomIPTransport;DBXInterBaseDriver;IndySystem;ibxbindings;bindcomp;FireDACCommon;IndyCore;RESTBackendComponents;bindcompdbx;rtl;RESTComponents;DBXSqliteDriver;IndyIPServer;dsnapxml;DataSnapClient;DataSnapProviderClient;DataSnapFireDAC;emsclientfiredac;FireDAC;FireDACDSDriver;xmlrtl;tethering;ibxpress;dsnap;CloudService;FMXTee;DataSnapNativeClient;PythonFmx;soaprtl;soapserver;FireDACIBDriver;$(DCC_UsePackage)</DCC_UsePackage>
<EnabledSysJars>annotation-1.2.0.dex.jar;asynclayoutinflater-1.0.0.dex.jar;billing-4.0.0.dex.jar;browser-1.0.0.dex.jar;cloud-messaging.dex.jar;collection-1.0.0.dex.jar;coordinatorlayout-1.0.0.dex.jar;core-1.5.0-rc02.dex.jar;core-common-2.0.1.dex.jar;core-runtime-2.0.1.dex.jar;cursoradapter-1.0.0.dex.jar;customview-1.0.0.dex.jar;documentfile-1.0.0.dex.jar;drawerlayout-1.0.0.dex.jar;firebase-annotations-16.0.0.dex.jar;firebase-common-20.0.0.dex.jar;firebase-components-17.0.0.dex.jar;firebase-datatransport-18.0.0.dex.jar;firebase-encoders-17.0.0.dex.jar;firebase-encoders-json-18.0.0.dex.jar;firebase-iid-interop-17.1.0.dex.jar;firebase-installations-17.0.0.dex.jar;firebase-installations-interop-17.0.0.dex.jar;firebase-measurement-connector-19.0.0.dex.jar;firebase-messaging-22.0.0.dex.jar;fmx.dex.jar;fragment-1.0.0.dex.jar;google-play-licensing.dex.jar;interpolator-1.0.0.dex.jar;javax.inject-1.dex.jar;legacy-support-core-ui-1.0.0.dex.jar;legacy-support-core-utils-1.0.0.dex.jar;lifecycle-common-2.0.0.dex.jar;lifecycle-livedata-2.0.0.dex.jar;lifecycle-livedata-core-2.0.0.dex.jar;lifecycle-runtime-2.0.0.dex.jar;lifecycle-service-2.0.0.dex.jar;lifecycle-viewmodel-2.0.0.dex.jar;listenablefuture-1.0.dex.jar;loader-1.0.0.dex.jar;localbroadcastmanager-1.0.0.dex.jar;play-services-ads-20.1.0.dex.jar;play-services-ads-base-20.1.0.dex.jar;play-services-ads-identifier-17.0.0.dex.jar;play-services-ads-lite-20.1.0.dex.jar;play-services-base-17.5.0.dex.jar;play-services-basement-17.6.0.dex.jar;play-services-cloud-messaging-16.0.0.dex.jar;play-services-drive-17.0.0.dex.jar;play-services-games-21.0.0.dex.jar;play-services-location-18.0.0.dex.jar;play-services-maps-17.0.1.dex.jar;play-services-measurement-base-18.0.0.dex.jar;play-services-measurement-sdk-api-18.0.0.dex.jar;play-services-places-placereport-17.0.0.dex.jar;play-services-stats-17.0.0.dex.jar;play-services-tasks-17.2.0.dex.jar;print-1.0.0.dex.jar;room-common-2.1.0.dex.jar;room-runtime-2.1.0.dex.jar;slidingpanelayout-1.0.0.dex.jar;sqlite-2.0.1.dex.jar;sqlite-framework-2.0.1.dex.jar;swiperefreshlayout-1.0.0.dex.jar;transport-api-3.0.0.dex.jar;transport-backend-cct-3.0.0.dex.jar;transport-runtime-3.0.0.dex.jar;user-messaging-platform-1.0.0.dex.jar;versionedparcelable-1.1.1.dex.jar;viewpager-1.0.0.dex.jar;work-runtime-2.1.0.dex.jar</EnabledSysJars>
</PropertyGroup>
<PropertyGroup Condition="'$(Base_iOSDevice64)'!=''">
<VerInfo_Keys>CFBundleName=$(MSBuildProjectName);CFBundleDevelopmentRegion=en;CFBundleDisplayName=$(MSBuildProjectName);CFBundleIdentifier=$(MSBuildProjectName);CFBundleInfoDictionaryVersion=7.1;CFBundleVersion=1.0.0;CFBundleShortVersionString=1.0.0;CFBundlePackageType=APPL;CFBundleSignature=????;LSRequiresIPhoneOS=true;CFBundleAllowMixedLocalizations=YES;CFBundleExecutable=$(MSBuildProjectName);UIDeviceFamily=iPhone &amp; iPad;NSLocationAlwaysUsageDescription=The reason for accessing the location information of the user;NSLocationWhenInUseUsageDescription=The reason for accessing the location information of the user;NSLocationAlwaysAndWhenInUseUsageDescription=The reason for accessing the location information of the user;UIBackgroundModes=;NSContactsUsageDescription=The reason for accessing the contacts;NSPhotoLibraryUsageDescription=The reason for accessing the photo library;NSPhotoLibraryAddUsageDescription=The reason for adding to the photo library;NSCameraUsageDescription=The reason for accessing the camera;NSFaceIDUsageDescription=The reason for accessing the face id;NSMicrophoneUsageDescription=The reason for accessing the microphone;NSSiriUsageDescription=The reason for accessing Siri;ITSAppUsesNonExemptEncryption=false;NSBluetoothAlwaysUsageDescription=The reason for accessing bluetooth;NSBluetoothPeripheralUsageDescription=The reason for accessing bluetooth peripherals;NSCalendarsUsageDescription=The reason for accessing the calendar data;NSRemindersUsageDescription=The reason for accessing the reminders;NSMotionUsageDescription=The reason for accessing the accelerometer;NSSpeechRecognitionUsageDescription=The reason for requesting to send user data to Apple&apos;s speech recognition servers</VerInfo_Keys>
<VerInfo_UIDeviceFamily>iPhoneAndiPad</VerInfo_UIDeviceFamily>
<VerInfo_IncludeVerInfo>true</VerInfo_IncludeVerInfo>
<BT_BuildType>Debug</BT_BuildType>
<VerInfo_BundleId>$(MSBuildProjectName)</VerInfo_BundleId>
</PropertyGroup>
<PropertyGroup Condition="'$(Base_iOSSimARM64)'!=''">
<VerInfo_Keys>CFBundleName=$(MSBuildProjectName);CFBundleDevelopmentRegion=en;CFBundleDisplayName=$(MSBuildProjectName);CFBundleIdentifier=$(MSBuildProjectName);CFBundleInfoDictionaryVersion=7.1;CFBundleVersion=1.0.0;CFBundleShortVersionString=1.0.0;CFBundlePackageType=APPL;CFBundleSignature=????;LSRequiresIPhoneOS=true;CFBundleAllowMixedLocalizations=YES;CFBundleExecutable=$(MSBuildProjectName);UIDeviceFamily=iPhone &amp; iPad;NSLocationAlwaysUsageDescription=The reason for accessing the location information of the user;NSLocationWhenInUseUsageDescription=The reason for accessing the location information of the user;NSLocationAlwaysAndWhenInUseUsageDescription=The reason for accessing the location information of the user;UIBackgroundModes=;NSContactsUsageDescription=The reason for accessing the contacts;NSPhotoLibraryUsageDescription=The reason for accessing the photo library;NSPhotoLibraryAddUsageDescription=The reason for adding to the photo library;NSCameraUsageDescription=The reason for accessing the camera;NSFaceIDUsageDescription=The reason for accessing the face id;NSMicrophoneUsageDescription=The reason for accessing the microphone;NSSiriUsageDescription=The reason for accessing Siri;ITSAppUsesNonExemptEncryption=false;NSBluetoothAlwaysUsageDescription=The reason for accessing bluetooth;NSBluetoothPeripheralUsageDescription=The reason for accessing bluetooth peripherals;NSCalendarsUsageDescription=The reason for accessing the calendar data;NSRemindersUsageDescription=The reason for accessing the reminders;NSMotionUsageDescription=The reason for accessing the accelerometer;NSSpeechRecognitionUsageDescription=The reason for requesting to send user data to Apple&apos;s speech recognition servers</VerInfo_Keys>
<VerInfo_UIDeviceFamily>iPhoneAndiPad</VerInfo_UIDeviceFamily>
<VerInfo_IncludeVerInfo>true</VerInfo_IncludeVerInfo>
</PropertyGroup>
<PropertyGroup Condition="'$(Base_Linux64)'!=''">
<DCC_UsePackage>DataSnapServer;fmx;emshosting;DbxCommonDriver;bindengine;FireDACCommonODBC;emsclient;FireDACCommonDriver;IndyProtocols;dbxcds;emsedge;inetdb;FireDACSqliteDriver;DbxClientDriver;FireDACASADriver;soapmidas;dbexpress;Python;FireDACInfxDriver;inet;DataSnapCommon;dbrtl;FireDACOracleDriver;CustomIPTransport;FireDACMSSQLDriver;DataSnapIndy10ServerTransport;DataSnapConnectors;FireDACMongoDBDriver;IndySystem;FireDACTDataDriver;bindcomp;FireDACCommon;DataSnapServerMidas;FireDACODBCDriver;emsserverresource;IndyCore;RESTBackendComponents;rtl;FireDACMySQLDriver;FireDACADSDriver;RESTComponents;dsnapxml;DataSnapClient;LockBoxDR;DataSnapFireDAC;emsclientfiredac;FireDACPgDriver;FireDAC;xmlrtl;dsnap;CloudService;FireDACDb2Driver;DataSnapNativeClient;DatasnapConnectorsFreePascal;soaprtl;soapserver;FireDACIBDriver;$(DCC_UsePackage)</DCC_UsePackage>
</PropertyGroup>
<PropertyGroup Condition="'$(Base_OSX64)'!=''">
<VerInfo_Keys>CFBundleName=$(MSBuildProjectName);CFBundleDisplayName=$(MSBuildProjectName);CFBundleIdentifier=$(MSBuildProjectName);CFBundleVersion=1.0.0;CFBundleShortVersionString=1.0.0;CFBundlePackageType=APPL;CFBundleSignature=????;CFBundleAllowMixedLocalizations=YES;CFBundleExecutable=$(MSBuildProjectName);NSHighResolutionCapable=true;LSApplicationCategoryType=public.app-category.utilities;NSLocationUsageDescription=The reason for accessing the location information of the user;NSContactsUsageDescription=The reason for accessing the contacts;NSCalendarsUsageDescription=The reason for accessing the calendar data;NSRemindersUsageDescription=The reason for accessing the reminders;NSCameraUsageDescription=The reason for accessing the camera;NSMicrophoneUsageDescription=The reason for accessing the microphone;NSMotionUsageDescription=The reason for accessing the accelerometer;NSDesktopFolderUsageDescription=The reason for accessing the Desktop folder;NSDocumentsFolderUsageDescription=The reason for accessing the Documents folder;NSDownloadsFolderUsageDescription=The reason for accessing the Downloads folder;NSNetworkVolumesUsageDescription=The reason for accessing files on a network volume;NSRemovableVolumesUsageDescription=The reason for accessing files on a removable volume;NSSpeechRecognitionUsageDescription=The reason for requesting to send user data to Apple&apos;s speech recognition servers;ITSAppUsesNonExemptEncryption=false;NSBluetoothAlwaysUsageDescription=The reason for accessing the Bluetooth interface</VerInfo_Keys>
<BT_BuildType>Debug</BT_BuildType>
</PropertyGroup>
<PropertyGroup Condition="'$(Base_OSXARM64)'!=''">
<VerInfo_Keys>CFBundleName=$(MSBuildProjectName);CFBundleDisplayName=$(MSBuildProjectName);CFBundleIdentifier=$(MSBuildProjectName);CFBundleVersion=1.0.0;CFBundleShortVersionString=1.0.0;CFBundlePackageType=APPL;CFBundleSignature=????;CFBundleAllowMixedLocalizations=YES;CFBundleExecutable=$(MSBuildProjectName);NSHighResolutionCapable=true;LSApplicationCategoryType=public.app-category.utilities;NSLocationUsageDescription=The reason for accessing the location information of the user;NSContactsUsageDescription=The reason for accessing the contacts;NSCalendarsUsageDescription=The reason for accessing the calendar data;NSRemindersUsageDescription=The reason for accessing the reminders;NSCameraUsageDescription=The reason for accessing the camera;NSMicrophoneUsageDescription=The reason for accessing the microphone;NSMotionUsageDescription=The reason for accessing the accelerometer;NSDesktopFolderUsageDescription=The reason for accessing the Desktop folder;NSDocumentsFolderUsageDescription=The reason for accessing the Documents folder;NSDownloadsFolderUsageDescription=The reason for accessing the Downloads folder;NSNetworkVolumesUsageDescription=The reason for accessing files on a network volume;NSRemovableVolumesUsageDescription=The reason for accessing files on a removable volume;NSSpeechRecognitionUsageDescription=The reason for requesting to send user data to Apple&apos;s speech recognition servers;ITSAppUsesNonExemptEncryption=false;NSBluetoothAlwaysUsageDescription=The reason for accessing the Bluetooth interface</VerInfo_Keys>
<BT_BuildType>Debug</BT_BuildType>
</PropertyGroup>
<PropertyGroup Condition="'$(Base_Win32)'!=''">
<DCC_UsePackage>RaizeComponentsVcl;JvNet;vclwinx;DataSnapServer;fmx;emshosting;vclie;DbxCommonDriver;bindengine;IndyIPCommon;VCLRESTComponents;DBXMSSQLDriver;FireDACCommonODBC;emsclient;FireDACCommonDriver;appanalytics;IndyProtocols;vclx;IndyIPClient;dbxcds;vcledge;bindcompvclwinx;FmxTeeUI;emsedge;bindcompfmx;DBXFirebirdDriver;JvBands;inetdb;JvAppFrm;ibmonitor;FireDACSqliteDriver;DbxClientDriver;FireDACASADriver;Tee;soapmidas;SVGIconImageListFMX;JclVcl;vclactnband;TeeUI;fmxFireDAC;dbexpress;Python;Jcl;FireDACInfxDriver;JvManagedThreads;DBXMySQLDriver;VclSmp;inet;DataSnapCommon;JvPascalInterpreter;PythonVcl;vcltouch;fmxase;JvPluginSystem;DBXOdbcDriver;JvDB;dbrtl;JvTimeFramework;FireDACDBXDriver;FireDACOracleDriver;fmxdae;TeeDB;FireDACMSAccDriver;JvCustom;CustomIPTransport;FireDACMSSQLDriver;SVGIconPackage;JvSystem;DataSnapIndy10ServerTransport;JclDeveloperTools;JvControls;DataSnapConnectors;vcldsnap;DBXInterBaseDriver;JvCrypt;FireDACMongoDBDriver;JvJans;JvMM;IndySystem;JvWizards;FireDACTDataDriver;JvGlobus;vcldb;ibxbindings;SynEditDR;JclContainers;JvPageComps;vclFireDAC;JvCore;bindcomp;FireDACCommon;DataSnapServerMidas;FireDACODBCDriver;emsserverresource;IndyCore;RESTBackendComponents;dmvcframeworkDT;bindcompdbx;rtl;FireDACMySQLDriver;FireDACADSDriver;RaizeComponentsVclDb;RESTComponents;DBXSqliteDriver;vcl;IndyIPServer;dsnapxml;dsnapcon;DataSnapClient;DataSnapProviderClient;adortl;JvDotNetCtrls;JvHMI;DBXSybaseASEDriver;LockBoxDR;JvRuntimeDesign;DBXDb2Driver;JvXPCtrls;vclimg;DataSnapFireDAC;emsclientfiredac;FireDACPgDriver;FireDAC;FireDACDSDriver;inetdbxpress;xmlrtl;tethering;dmvcframeworkRT;ibxpress;JvStdCtrls;JvDlgs;bindcompvcl;dsnap;JvDocking;JvCmp;JvPrintPreview;CloudService;DBXSybaseASADriver;DBXOracleDriver;FireDACDb2Driver;DBXInformixDriver;vclib;fmxobj;bindcompvclsmp;FMXTee;DataSnapNativeClient;PythonFmx;DatasnapConnectorsFreePascal;soaprtl;SVGIconImageList;soapserver;FireDACIBDriver;$(DCC_UsePackage)</DCC_UsePackage>
<DCC_Namespace>Winapi;System.Win;Data.Win;Datasnap.Win;Web.Win;Soap.Win;Xml.Win;Bde;$(DCC_Namespace)</DCC_Namespace>
@ -258,6 +299,16 @@
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="AndroidSplashImageDefV21">
<Platform Name="Android">
<RemoteDir>res\drawable-anydpi-v21</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="Android64">
<RemoteDir>res\drawable-anydpi-v21</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="AndroidSplashStyles">
<Platform Name="Android">
<RemoteDir>res\values</RemoteDir>
@ -278,6 +329,66 @@
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="AndroidSplashStylesV31">
<Platform Name="Android">
<RemoteDir>res\values-v31</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="Android64">
<RemoteDir>res\values-v31</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="Android_AdaptiveIcon">
<Platform Name="Android">
<RemoteDir>res\drawable-anydpi-v26</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="Android64">
<RemoteDir>res\drawable-anydpi-v26</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="Android_AdaptiveIconBackground">
<Platform Name="Android">
<RemoteDir>res\drawable</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="Android64">
<RemoteDir>res\drawable</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="Android_AdaptiveIconForeground">
<Platform Name="Android">
<RemoteDir>res\drawable</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="Android64">
<RemoteDir>res\drawable</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="Android_AdaptiveIconMonochrome">
<Platform Name="Android">
<RemoteDir>res\drawable</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="Android64">
<RemoteDir>res\drawable</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="Android_AdaptiveIconV33">
<Platform Name="Android">
<RemoteDir>res\drawable-anydpi-v33</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="Android64">
<RemoteDir>res\drawable-anydpi-v33</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="Android_Colors">
<Platform Name="Android">
<RemoteDir>res\values</RemoteDir>
@ -288,6 +399,16 @@
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="Android_ColorsDark">
<Platform Name="Android">
<RemoteDir>res\values-night-v21</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="Android64">
<RemoteDir>res\values-night-v21</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="Android_DefaultAppIcon">
<Platform Name="Android">
<RemoteDir>res\drawable</RemoteDir>
@ -458,6 +579,56 @@
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="Android_VectorizedNotificationIcon">
<Platform Name="Android">
<RemoteDir>res\drawable-anydpi-v24</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="Android64">
<RemoteDir>res\drawable-anydpi-v24</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="Android_VectorizedSplash">
<Platform Name="Android">
<RemoteDir>res\drawable</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="Android64">
<RemoteDir>res\drawable</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="Android_VectorizedSplashDark">
<Platform Name="Android">
<RemoteDir>res\drawable-night-anydpi-v21</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="Android64">
<RemoteDir>res\drawable-night-anydpi-v21</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="Android_VectorizedSplashV31">
<Platform Name="Android">
<RemoteDir>res\drawable-anydpi-v31</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="Android64">
<RemoteDir>res\drawable-anydpi-v31</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="Android_VectorizedSplashV31Dark">
<Platform Name="Android">
<RemoteDir>res\drawable-night-anydpi-v31</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="Android64">
<RemoteDir>res\drawable-night-anydpi-v31</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="DebugSymbols">
<Platform Name="iOSSimulator">
<Operation>1</Operation>
@ -563,6 +734,130 @@
<Operation>0</Operation>
</Platform>
</DeployClass>
<DeployClass Name="ProjectAndroidManifest">
<Platform Name="Android">
<Operation>1</Operation>
</Platform>
<Platform Name="Android64">
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="ProjectOSXDebug"/>
<DeployClass Name="ProjectOSXEntitlements"/>
<DeployClass Name="ProjectOSXInfoPList"/>
<DeployClass Name="ProjectOSXResource">
<Platform Name="OSX32">
<RemoteDir>Contents\Resources</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="OSX64">
<RemoteDir>Contents\Resources</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="OSXARM64">
<RemoteDir>Contents\Resources</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Required="true" Name="ProjectOutput">
<Platform Name="Android">
<RemoteDir>library\lib\armeabi-v7a</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="Android64">
<RemoteDir>library\lib\arm64-v8a</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="iOSDevice32">
<Operation>1</Operation>
</Platform>
<Platform Name="iOSDevice64">
<Operation>1</Operation>
</Platform>
<Platform Name="iOSSimARM64">
<Operation>1</Operation>
</Platform>
<Platform Name="Linux64">
<Operation>1</Operation>
</Platform>
<Platform Name="OSX32">
<Operation>1</Operation>
</Platform>
<Platform Name="OSX64">
<Operation>1</Operation>
</Platform>
<Platform Name="OSXARM64">
<Operation>1</Operation>
</Platform>
<Platform Name="Win32">
<Operation>0</Operation>
</Platform>
</DeployClass>
<DeployClass Name="ProjectOutput_Android32">
<Platform Name="Android64">
<RemoteDir>library\lib\armeabi-v7a</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="ProjectUWPManifest">
<Platform Name="Win32">
<Operation>1</Operation>
</Platform>
<Platform Name="Win64">
<Operation>1</Operation>
</Platform>
<Platform Name="Win64x">
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="ProjectiOSDeviceDebug">
<Platform Name="iOSDevice32">
<RemoteDir>..\$(PROJECTNAME).app.dSYM\Contents\Resources\DWARF</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="iOSDevice64">
<RemoteDir>..\$(PROJECTNAME).app.dSYM\Contents\Resources\DWARF</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="iOSSimARM64">
<RemoteDir>..\$(PROJECTNAME).app.dSYM\Contents\Resources\DWARF</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="ProjectiOSEntitlements"/>
<DeployClass Name="ProjectiOSInfoPList"/>
<DeployClass Name="ProjectiOSLaunchScreen"/>
<DeployClass Name="ProjectiOSResource">
<Platform Name="iOSDevice32">
<Operation>1</Operation>
</Platform>
<Platform Name="iOSDevice64">
<Operation>1</Operation>
</Platform>
<Platform Name="iOSSimARM64">
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="UWP_DelphiLogo150">
<Platform Name="Win32">
<RemoteDir>Assets</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="Win64">
<RemoteDir>Assets</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="UWP_DelphiLogo44">
<Platform Name="Win32">
<RemoteDir>Assets</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="Win64">
<RemoteDir>Assets</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="iOS_AppStore1024">
<Platform Name="iOSDevice64">
<RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset</RemoteDir>
@ -763,127 +1058,6 @@
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="ProjectAndroidManifest">
<Platform Name="Android">
<Operation>1</Operation>
</Platform>
<Platform Name="Android64">
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="ProjectiOSDeviceDebug">
<Platform Name="iOSDevice32">
<RemoteDir>..\$(PROJECTNAME).app.dSYM\Contents\Resources\DWARF</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="iOSDevice64">
<RemoteDir>..\$(PROJECTNAME).app.dSYM\Contents\Resources\DWARF</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="iOSSimARM64">
<RemoteDir>..\$(PROJECTNAME).app.dSYM\Contents\Resources\DWARF</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="ProjectiOSEntitlements"/>
<DeployClass Name="ProjectiOSInfoPList"/>
<DeployClass Name="ProjectiOSLaunchScreen"/>
<DeployClass Name="ProjectiOSResource">
<Platform Name="iOSDevice32">
<Operation>1</Operation>
</Platform>
<Platform Name="iOSDevice64">
<Operation>1</Operation>
</Platform>
<Platform Name="iOSSimARM64">
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="ProjectOSXDebug"/>
<DeployClass Name="ProjectOSXEntitlements"/>
<DeployClass Name="ProjectOSXInfoPList"/>
<DeployClass Name="ProjectOSXResource">
<Platform Name="OSX32">
<RemoteDir>Contents\Resources</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="OSX64">
<RemoteDir>Contents\Resources</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="OSXARM64">
<RemoteDir>Contents\Resources</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Required="true" Name="ProjectOutput">
<Platform Name="Android">
<RemoteDir>library\lib\armeabi-v7a</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="Android64">
<RemoteDir>library\lib\arm64-v8a</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="iOSDevice32">
<Operation>1</Operation>
</Platform>
<Platform Name="iOSDevice64">
<Operation>1</Operation>
</Platform>
<Platform Name="iOSSimARM64">
<Operation>1</Operation>
</Platform>
<Platform Name="Linux64">
<Operation>1</Operation>
</Platform>
<Platform Name="OSX32">
<Operation>1</Operation>
</Platform>
<Platform Name="OSX64">
<Operation>1</Operation>
</Platform>
<Platform Name="OSXARM64">
<Operation>1</Operation>
</Platform>
<Platform Name="Win32">
<Operation>0</Operation>
</Platform>
</DeployClass>
<DeployClass Name="ProjectOutput_Android32">
<Platform Name="Android64">
<RemoteDir>library\lib\armeabi-v7a</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="ProjectUWPManifest">
<Platform Name="Win32">
<Operation>1</Operation>
</Platform>
<Platform Name="Win64">
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="UWP_DelphiLogo150">
<Platform Name="Win32">
<RemoteDir>Assets</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="Win64">
<RemoteDir>Assets</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="UWP_DelphiLogo44">
<Platform Name="Win32">
<RemoteDir>Assets</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="Win64">
<RemoteDir>Assets</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<ProjectRoot Platform="Android" Name="$(PROJECTNAME)"/>
<ProjectRoot Platform="Android64" Name="$(PROJECTNAME)"/>
<ProjectRoot Platform="iOSDevice32" Name="$(PROJECTNAME).app"/>
@ -895,11 +1069,16 @@
<ProjectRoot Platform="OSXARM64" Name="$(PROJECTNAME)"/>
<ProjectRoot Platform="Win32" Name="$(PROJECTNAME)"/>
<ProjectRoot Platform="Win64" Name="$(PROJECTNAME)"/>
<ProjectRoot Platform="Win64x" Name="$(PROJECTNAME)"/>
</Deployment>
<Platforms>
<Platform value="Android">False</Platform>
<Platform value="Android64">False</Platform>
<Platform value="iOSDevice64">False</Platform>
<Platform value="iOSSimARM64">False</Platform>
<Platform value="Linux64">False</Platform>
<Platform value="OSX64">False</Platform>
<Platform value="OSXARM64">False</Platform>
<Platform value="Win32">True</Platform>
<Platform value="Win64">False</Platform>
</Platforms>

View File

@ -1,60 +0,0 @@
program ServerSideViewsCustomEngine;
{$APPTYPE CONSOLE}
uses
System.SysUtils,
{$IFDEF MSWINDOWS}
Winapi.ShellAPI,
Winapi.Windows,
{$ENDIF }
IdHTTPWebBrokerBridge,
Web.WebReq,
Web.WebBroker,
WebModuleU in 'WebModuleU.pas' {WebModule1: TWebModule},
WebSiteControllerU in 'WebSiteControllerU.pas',
MVCFramework.View.Renderers.TemplatePro in 'MVCFramework.View.Renderers.TemplatePro.pas',
TemplateProU in 'lib\TemplateProU.pas',
MyDataModuleU in '..\renders\MyDataModuleU.pas' {MyDataModule: TDataModule},
MVCFramework.Cache in '..\..\sources\MVCFramework.Cache.pas',
MVCFramework.Serializer.HTML in '..\..\sources\MVCFramework.Serializer.HTML.pas';
{$R *.res}
procedure RunServer(APort: Integer);
var
LServer: TIdHTTPWebBrokerBridge;
begin
ReportMemoryLeaksOnShutdown := True;
Writeln(Format('Starting HTTP Server on port %d', [APort]));
LServer := TIdHTTPWebBrokerBridge.Create(nil);
try
LServer.DefaultPort := APort;
LServer.Active := True;
Writeln('Press RETURN to stop the server');
{$IFDEF MSWINDOWS}
ShellExecute(0, 'open', 'http://localhost:8080', nil, nil, SW_SHOW);
{$ENDIF}
ReadLn;
finally
LServer.Free;
end;
end;
begin
try
if WebRequestHandler <> nil then
WebRequestHandler.WebModuleClass := WebModuleClass;
RunServer(8080);
except
on E: Exception do
Writeln(E.ClassName, ': ', E.Message);
end;
end.

View File

@ -1,40 +0,0 @@
unit WebSiteControllerU;
interface
uses
MVCFramework, System.Diagnostics, System.JSON, MVCFramework.Commons;
type
[MVCPath('/')]
TWebSiteController = class(TMVCController)
public
[MVCPath('/')]
[MVCHTTPMethods([httpGET])]
procedure Index;
end;
implementation
{ TWebSiteController }
uses System.SysUtils, Web.HTTPApp, MyDataModuleU;
procedure TWebSiteController.Index;
var
lDM: TMyDataModule;
begin
ContentType := BuildContentType(TMVCMediaType.TEXT_HTML, tmvcCharSet.UTF_8);
lDM := TMyDataModule.Create(nil);
try
lDM.qryCustomers.Open;
ViewData['people'] := lDM.qryCustomers;
LoadView(['header', 'people_list', 'footer']);
finally
lDM.Free;
end;
RenderResponseStream; // rember to call RenderResponseStream!!!
end;
end.

View File

@ -1 +0,0 @@
[{"first_name":"Scott","last_name":"Summers","age":35,"items":"","guid":""},{"first_name":"Bruce","last_name":"Banner","age":56,"items":"","guid":""},{"first_name":"ITDevConGuy","last_name":"Hello","age":36,"items":"","guid":""},{"first_name":"Hello","last_name":"World","age":56,"items":"","guid":"{556B6DA7-F0FF-4261-92D1-8477DACF1542}"}]

View File

@ -1,36 +0,0 @@
<div class="row_fluid">
<div class="col-sm-12">
<table class="table table-striped">
<thead>
<tr>
<th>Customer</th>
<th>First Name</th>
<th>Last Name</th>
<th>&nbsp</th>
</tr>
</thead>
<tbody>
{{loop(people)}}
<tr>
<td>{{:people.customer}}</td>
<td>{{:people.contact_first}}</td>
<td>{{:people.contact_last}}</td>
<td class="text-right">
<a class="btn btn-default" href="javascript:alert('Not implemented, check the main demo');">
<span class="glyphicon glyphicon-pencil"></span> View</a>
</td>
</tr>
{{endloop}}
</tbody>
</table>
</div>
</div>
<br>
<div class="row_fluid">
<div class="col-sm-2">
<a class="btn btn-default btn-block" href="javascript:alert('Not implemented, check the main demo');">Export as CSV</a>
</div>
<div class="col-sm-2 col-sm-offset-8">
<a class="btn btn-primary btn-block" href="javascript:alert('Not implemented, check the main demo');">Add New Person</a>
</div>
</div>

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,58 @@
// ***************************************************************************
//
// Delphi MVC Framework
//
// Copyright (c) 2010-2024 Daniele Teti and the DMVCFramework Team
//
// https://github.com/danieleteti/delphimvcframework
//
// ***************************************************************************
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// ***************************************************************************
unit CustomTemplateProFiltersU;
interface
//uses
// mormot.core.mustache;
type
TMyMustacheHelpers = class sealed
public
class procedure MyHelper1(const Value: variant; out Result: variant);
class procedure MyHelper2(const Value: variant; out Result: variant);
end;
implementation
uses
MVCFramework.View.Renderers.Mustache, System.SysUtils;
{ TMyMustacheHelpers }
class procedure TMyMustacheHelpers.MyHelper1(const Value: variant;
out Result: variant);
begin
Result := Value + ' (I''m The MyHelper1)';
end;
class procedure TMyMustacheHelpers.MyHelper2(const Value: variant; out Result: variant);
begin
Result := Value + ' (I''m The MyHelper2)';
end;
end.

View File

@ -0,0 +1,279 @@
unit DAL;
interface
uses
System.JSON,
MVCFramework.SystemJSONUtils,
System.Generics.Collections,
MVCFramework.Serializer.Commons;
type
[MVCNameCase(ncLowerCase)]
TPerson = class
private
FFirstName: string;
FLastName: string;
FAge: Integer;
FDevices: TArray<string>;
FGUID: string;
procedure SetFirstName(const Value: string);
procedure SetLastName(const Value: string);
procedure SetAge(const Value: Integer);
procedure SetGUID(const Value: string);
procedure SetDevices(const Value: TArray<string>);
public
[MVCNameAs('first_name')]
property FirstName: string read FFirstName write SetFirstName;
[MVCNameAs('last_name')]
property LastName: string read FLastName write SetLastName;
property Age: Integer read FAge write SetAge;
property Devices: TArray<string> read FDevices write SetDevices;
property GUID: string read FGUID write SetGUID;
end;
TPeople = class(TObjectList<TPerson>)
end;
{$M+}
TMyObj = class
private
FRawHTML: String;
procedure SetRawHTML(const Value: String);
published
property RawHTML: String read FRawHTML write SetRawHTML;
end;
{$M-}
TDevice = class
private
fDeviceName: string;
fSelected: Boolean;
public
property DeviceName: string read fDeviceName write fDeviceName;
property Selected: Boolean read fSelected write fSelected;
constructor Create(aDeviceName: string; aSelected: Boolean);
end;
TDeviceList = class(TObjectList<TDevice>)
public
function Contains(const aDeviceName: string): Boolean;
function IndexOf(const aDeviceName: string): Integer;
end;
IPeopleDAL = interface
['{3E534A3E-EAEB-44ED-B74E-EFBBAAAE11B4}']
function GetPeople: TPeople;
procedure AddPerson(FirstName, LastName: string; Age: Integer;
Items: TArray<string>);
procedure DeleteByGUID(GUID: string);
function GetPersonByGUID(GUID: string): TPerson;
function GetDevicesList: TArray<String>;
end;
TPeopleDAL = class(TInterfacedObject, IPeopleDAL)
private const
DATAFILE: string = 'people.data';
public
function GetPeople: TPeople;
procedure AddPerson(FirstName, LastName: string; Age: Integer;
Items: TArray<string>);
procedure DeleteByGUID(GUID: string);
function GetPersonByGUID(GUID: string): TPerson;
function GetDevicesList: TArray<String>;
end;
TServicesFactory = class sealed
class function GetPeopleDAL: IPeopleDAL;
end;
implementation
uses
System.SyncObjs,
System.IOUtils,
MVCFramework.Serializer.Defaults,
System.SysUtils;
var
// Hey! The storage is a simple json file, so some synchronization is needed
_CS: TCriticalSection = nil;
{ TSimpleDAL }
procedure TPeopleDAL.AddPerson(FirstName, LastName: string; Age: Integer;
Items: TArray<string>);
var
lPeople: TPeople;
lPerson: TPerson;
begin
_CS.Enter;
try
lPeople := GetPeople;
try
lPerson := TPerson.Create;
lPeople.Add(lPerson);
lPerson.FirstName := FirstName;
lPerson.LastName := LastName;
lPerson.Age := Age;
lPerson.Devices := Items;
lPerson.GUID := TGuid.NewGuid.ToString.Replace('{', '').Replace('}', '')
.Replace('-', '');
TFile.WriteAllText(DATAFILE, GetDefaultSerializer.SerializeCollection
(lPeople));
finally
lPeople.Free;
end;
finally
_CS.Leave;
end;
end;
class function TServicesFactory.GetPeopleDAL: IPeopleDAL;
begin
Result := TPeopleDAL.Create;
end;
procedure TPeopleDAL.DeleteByGUID(GUID: string);
var
LJPeople: TPeople;
I: Integer;
begin
_CS.Enter;
try
LJPeople := GetPeople;
try
for I := 0 to LJPeople.Count - 1 do
begin
if LJPeople[I].GUID = GUID then
begin
LJPeople.Delete(I);
break;
end;
end;
TFile.WriteAllText(DATAFILE, GetDefaultSerializer.SerializeCollection
(LJPeople));
finally
LJPeople.Free;
end;
finally
_CS.Leave;
end;
end;
function TPeopleDAL.GetDevicesList: TArray<String>;
begin
Result := ['smartphone', 'dumbphone', 'laptop', 'desktop'];
end;
function TPeopleDAL.GetPeople: TPeople;
var
LData: string;
begin
_CS.Enter;
try
Result := TPeople.Create;
if TFile.Exists(DATAFILE) then
LData := TFile.ReadAllText(DATAFILE).Trim;
if not LData.IsEmpty then
begin
GetDefaultSerializer.DeserializeCollection(LData, Result, TPerson);
end;
finally
_CS.Leave;
end;
end;
function TPeopleDAL.GetPersonByGUID(GUID: string): TPerson;
var
lPeople: TPeople;
lPerson: TPerson;
begin
Result := nil;
lPeople := GetPeople;
try
for lPerson in lPeople do
begin
if lPerson.GUID = GUID then
begin
Result := lPeople.Extract(lPerson);
break;
end;
end;
finally
lPeople.Free;
end;
end;
{ TPerson }
procedure TPerson.SetAge(const Value: Integer);
begin
FAge := Value;
end;
procedure TPerson.SetDevices(const Value: TArray<string>);
begin
FDevices := Value;
end;
procedure TPerson.SetFirstName(const Value: string);
begin
FFirstName := Value;
end;
procedure TPerson.SetGUID(const Value: string);
begin
FGUID := Value;
end;
procedure TPerson.SetLastName(const Value: string);
begin
FLastName := Value;
end;
{ TDevice }
constructor TDevice.Create(aDeviceName: string; aSelected: Boolean);
begin
inherited Create;
fDeviceName := aDeviceName;
fSelected := aSelected;
end;
{ TDeviceList }
function TDeviceList.Contains(const aDeviceName: string): Boolean;
begin
Result := IndexOf(aDeviceName) > -1;
end;
function TDeviceList.IndexOf(const aDeviceName: string): Integer;
var
I: Integer;
begin
Result := -1;
for I := 0 to Self.Count - 1 do
begin
if SameText(Self[I].DeviceName, aDeviceName) then
Exit(I);
end;
end;
{ TRawObj }
procedure TMyObj.SetRawHTML(const Value: String);
begin
FRawHTML := Value;
end;
initialization
_CS := TCriticalSection.Create;
finalization
_CS.Free;
end.

View File

@ -0,0 +1,68 @@
program ServerSideViewsTemplatePro;
{$APPTYPE CONSOLE}
uses
System.SysUtils,
MVCFramework,
MVCFramework.Signal,
MVCFramework.Logger,
{$IFDEF MSWINDOWS}
Winapi.ShellAPI,
Winapi.Windows,
{$ENDIF }
IdHTTPWebBrokerBridge,
MVCFramework.View.Renderers.TemplatePro,
Web.WebReq,
Web.WebBroker,
WebModuleU in 'WebModuleU.pas' {WebModule1: TWebModule},
WebSiteControllerU in 'WebSiteControllerU.pas',
DAL in 'DAL.pas',
MyDataModuleU in '..\renders\MyDataModuleU.pas' {MyDataModule: TDataModule},
CustomTemplateProFiltersU in 'CustomTemplateProFiltersU.pas',
TemplatePro in '..\..\..\templatepro\TemplatePro.pas';
{$R *.res}
procedure RunServer(APort: Integer);
var
LServer: TIdHTTPWebBrokerBridge;
begin
ReportMemoryLeaksOnShutdown := True;
LogI(Format('Starting HTTP Server on port %d', [APort]));
LServer := TIdHTTPWebBrokerBridge.Create(nil);
try
LServer.DefaultPort := APort;
LServer.Active := True;
{$IFDEF MSWINDOWS}
ShellExecute(0, 'open', PChar('http://localhost:' + inttostr(APort)), nil, nil, SW_SHOW);
{$ENDIF}
LogI('Ctrl+C to stop the server');
WaitForTerminationSignal;
EnterInShutdownState;
LServer.Active := False;
finally
LServer.Free;
end;
end;
begin
ReportMemoryLeaksOnShutdown := True;
try
if WebRequestHandler <> nil then
WebRequestHandler.WebModuleClass := WebModuleClass;
// these helpers will be available to the mustache views as if they were the standard ones
// TMVCMustacheHelpers.OnLoadCustomHelpers := procedure(var MustacheHelpers: TSynMustacheHelpers)
// begin
// TSynMustache.HelperAdd(MustacheHelpers, 'MyHelper1', TMyMustacheHelpers.MyHelper1);
// TSynMustache.HelperAdd(MustacheHelpers, 'MyHelper2', TMyMustacheHelpers.MyHelper2);
// end;
RunServer(8080);
except
on E: Exception do
LogE(E.ClassName + ': ' + E.Message);
end;
end.

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,23 @@
object WebModule1: TWebModule1
OnCreate = WebModuleCreate
OnDestroy = WebModuleDestroy
Actions = <
item
Default = True
Name = 'DefaultHandler'
PathInfo = '/'
end>
Height = 230
Width = 415
object FDMemTable1: TFDMemTable
FetchOptions.AssignedValues = [evMode]
FetchOptions.Mode = fmAll
ResourceOptions.AssignedValues = [rvSilentMode]
ResourceOptions.SilentMode = True
UpdateOptions.AssignedValues = [uvCheckRequired, uvAutoCommitUpdates]
UpdateOptions.CheckRequired = False
UpdateOptions.AutoCommitUpdates = True
Left = 192
Top = 96
end
end

View File

@ -2,10 +2,13 @@ unit WebModuleU;
interface
uses System.SysUtils, System.Classes, Web.HTTPApp, MVCFramework;
uses System.SysUtils, System.Classes, Web.HTTPApp, MVCFramework, FireDAC.Stan.Intf, FireDAC.Stan.Option,
FireDAC.Stan.Param, FireDAC.Stan.Error, FireDAC.DatS, FireDAC.Phys.Intf, FireDAC.DApt.Intf, Data.DB,
FireDAC.Comp.DataSet, FireDAC.Comp.Client;
type
TWebModule1 = class(TWebModule)
FDMemTable1: TFDMemTable;
procedure WebModuleCreate(Sender: TObject);
procedure WebModuleDestroy(Sender: TObject);
private
@ -23,13 +26,17 @@ implementation
uses
MVCFramework.View.Renderers.TemplatePro,
WebSiteControllerU,
System.IOUtils,
MVCFramework.Commons,
MVCFramework.Middleware.StaticFiles;
MVCFramework.Middleware.StaticFiles,
CustomTemplateProFiltersU,
MVCFramework.Serializer.URLEncoded;
{ %CLASSGROUP 'Vcl.Controls.TControl' }
{$R *.dfm}
procedure TWebModule1.WebModuleCreate(Sender: TObject);
begin
FMVCEngine := TMVCEngine.Create(Self,
@ -46,14 +53,16 @@ begin
// unhandled actions are permitted?
Config[TMVCConfigKey.AllowUnhandledAction] := 'false';
// default view file extension
Config[TMVCConfigKey.DefaultViewFileExtension] := 'html';
Config[TMVCConfigKey.DefaultViewFileExtension] := 'tpro';
// view path
Config[TMVCConfigKey.ViewPath] := 'templates';
// Enable Server Signature in response
Config[TMVCConfigKey.ExposeServerSignature] := 'true';
Config[TMVCConfigKey.ViewCache] := 'false';
end)
.AddController(TWebSiteController)
.SetViewEngine(TMVCTemplateProViewEngine);
.SetViewEngine(TMVCTemplateProViewEngine)
.AddSerializer(TMVCMediaType.APPLICATION_FORM_URLENCODED, TMVCURLEncodedSerializer.Create);
end;
procedure TWebModule1.WebModuleDestroy(Sender: TObject);

View File

@ -0,0 +1,267 @@
unit WebSiteControllerU;
interface
uses
MVCFramework, System.Diagnostics, JsonDataObjects, MVCFramework.Commons, DAL,
System.Generics.Collections;
type
[MVCPath('/')]
TWebSiteController = class(TMVCController)
protected
procedure OnBeforeAction(Context: TWebContext; const AActionNAme: string;
var Handled: Boolean); override;
function GeneratePeopleListAsCSV: String;
public
[MVCPath('/people')]
[MVCHTTPMethods([httpGET])]
[MVCProduces(TMVCMediaType.TEXT_HTML)]
function PeopleList: String;
[MVCPath('/people')]
[MVCHTTPMethods([httpGET])]
[MVCProduces(TMVCMediaType.TEXT_CSV)]
// RESTful API, requires ACCEPT=text/csv
function ExportPeopleListAsCSV_API: String;
[MVCPath('/people/formats/csv')]
[MVCHTTPMethods([httpGET])]
// Route usable by the browser, doesn't requires ACCEPT=text/csv
function ExportPeopleListAsCSV: String;
[MVCPath('/people')]
[MVCHTTPMethods([httpPOST])]
[MVCConsumes(TMVCMediaType.APPLICATION_FORM_URLENCODED)]
procedure SavePerson(const [MVCFromBody] Person: TPerson);
[MVCPath('/deleteperson')]
[MVCHTTPMethods([httpPOST])]
[MVCConsumes(TMVCMediaType.APPLICATION_FORM_URLENCODED)]
procedure DeletePerson([MVCFromContentField('guid')] const GUID: String);
[MVCPath('/new')]
[MVCHTTPMethods([httpGET])]
[MVCProduces(TMVCMediaType.TEXT_HTML)]
function NewPerson: String;
[MVCPath('/edit/($guid)')]
[MVCHTTPMethods([httpGET])]
[MVCProduces(TMVCMediaType.TEXT_HTML)]
function EditPerson(guid: string): String;
[MVCPath('/')]
[MVCHTTPMethods([httpGET])]
[MVCProduces(TMVCMediaType.TEXT_HTML)]
procedure Index;
[MVCPath('/showcase')]
[MVCHTTPMethods([httpGET])]
[MVCProduces(TMVCMediaType.TEXT_HTML)]
function MustacheTemplateShowCase: String;
[MVCPath('/loadviewtest')]
[MVCHTTPMethods([httpGET])]
[MVCProduces(TMVCMediaType.TEXT_PLAIN)]
procedure LoadViewTest;
end;
implementation
{ TWebSiteController }
uses System.SysUtils, Web.HTTPApp, FireDAC.Stan.Intf, FireDAC.Stan.Option,
FireDAC.Stan.Param, FireDAC.Stan.Error, FireDAC.DatS, FireDAC.Phys.Intf, FireDAC.DApt.Intf, Data.DB,
FireDAC.Comp.DataSet, FireDAC.Comp.Client;
procedure TWebSiteController.DeletePerson(const GUID: String);
var
LDAL: IPeopleDAL;
begin
LDAL := TServicesFactory.GetPeopleDAL;
LDAL.DeleteByGUID(GUID);
Redirect('/people');
end;
function TWebSiteController.EditPerson(guid: string): String;
var
LDAL: IPeopleDAL;
lPerson: TPerson;
lDevices: TArray<String>;
lItem: string;
lDeviceList: TDeviceList;
lSelected: Boolean;
begin
LDAL := TServicesFactory.GetPeopleDAL;
lPerson := LDAL.GetPersonByGUID(guid);
try
ViewData['person'] := lPerson;
lDevices := LDAL.GetDevicesList;
lDeviceList := TDeviceList.Create;
try
for lItem in lDevices do
begin
lSelected := TArray.Contains<String>(lPerson.Devices, lItem);
lDeviceList.Add(TDevice.Create(lItem, lSelected))
end;
ViewData['devices'] := lDeviceList;
Result := Page(['editperson']);
finally
lDeviceList.Free;
end;
finally
lPerson.Free;
end;
end;
function TWebSiteController.ExportPeopleListAsCSV: String;
begin
Result := GeneratePeopleListAsCSV;
// define the correct behaviour to download the csv inside the browser
ContentType := TMVCMediaType.TEXT_CSV;
Context.Response.CustomHeaders.Values['Content-Disposition'] :=
'attachment; filename=people.csv';
end;
function TWebSiteController.ExportPeopleListAsCSV_API: String;
begin
Result := GeneratePeopleListAsCSV;
end;
function TWebSiteController.GeneratePeopleListAsCSV: String;
var
LDAL: IPeopleDAL;
lPeople: TPeople;
begin
LDAL := TServicesFactory.GetPeopleDAL;
lPeople := LDAL.GetPeople;
try
ViewData['people'] := lPeople;
Result := PageFragment(['people_header.csv', 'people_list.csv']);
finally
lPeople.Free;
end;
end;
procedure TWebSiteController.Index;
begin
Redirect('/people');
end;
procedure TWebSiteController.LoadViewTest;
var
lDS: TFDMemTable;
begin
lDS := TFDMemTable.Create(nil);
try
lDS.FieldDefs.Add('id', ftInteger);
lDS.FieldDefs.Add('first_name', ftString, 40);
lDS.FieldDefs.Add('last_name', ftString, 40);
lDS.FieldDefs.Add('age', ftInteger);
lDS.CreateDataSet;
lDS.AppendRecord([1,'Daniele','Teti',44]);
lDS.AppendRecord([2,'Bruce','Banner',54]);
lDS.AppendRecord([3,'Peter','Parker',34]);
lDS.First;
ViewData['people'] := lDS;
LoadView(['people_list_test','people_list_test']);
RenderResponseStream;
finally
lDS.Free;
end;
end;
function TWebSiteController.MustacheTemplateShowCase: String;
var
LDAL: IPeopleDAL;
lPeople, lPeople2: TPeople;
lMyObj: TMyObj;
begin
LDAL := TServicesFactory.GetPeopleDAL;
lPeople := LDAL.GetPeople;
try
lPeople2 := TPeople.Create;
try
lMyObj := TMyObj.Create;
try
lMyObj.RawHTML := '<h1>This is</h1>Raw<br><span>HTML</span>';
ViewData['people'] := lPeople;
ViewData['people2'] := lPeople2;
ViewData['myobj'] := lMyObj;
Result := Page(['showcase'], False);
finally
lMyObj.Free;
end;
finally
lPeople2.Free;
end;
finally
lPeople.Free;
end;
end;
function TWebSiteController.NewPerson: String;
var
LDAL: IPeopleDAL;
lDevices: TArray<String>;
lDeviceList: TDeviceList;
lItem: String;
begin
LDAL := TServicesFactory.GetPeopleDAL;
lDevices := LDAL.GetDevicesList;
lDeviceList := TDeviceList.Create;
try
for lItem in lDevices do
begin
lDeviceList.Add(TDevice.Create(lItem, False))
end;
ViewData['devices'] := lDeviceList;
Result := Page(['editperson']);
finally
lDeviceList.Free;
end;
end;
procedure TWebSiteController.OnBeforeAction(Context: TWebContext;
const AActionNAme: string; var Handled: Boolean);
begin
inherited;
if not AActionNAme.ToLower.Contains('test') then ContentType := 'text/html';
Handled := False;
end;
function TWebSiteController.PeopleList: String;
var
LDAL: IPeopleDAL;
lPeople: TPeople;
begin
LDAL := TServicesFactory.GetPeopleDAL;
lPeople := LDAL.GetPeople;
try
ViewData['people'] := lPeople;
Result := Page(['people_list']);
finally
lPeople.Free;
end;
end;
procedure TWebSiteController.SavePerson(const [MVCFromBody] Person: TPerson);
var
LPeopleDAL: IPeopleDAL;
begin
if Person.FirstName.IsEmpty or Person.LastName.IsEmpty or (Person.Age <= 0) then
begin
{ TODO -oDaniele -cGeneral : Show how to properly render an exception }
raise EMVCException.Create('Invalid data',
'First name, last name and age are not optional', 0);
end;
LPeopleDAL := TServicesFactory.GetPeopleDAL;
LPeopleDAL.AddPerson(Person.FirstName, Person.LastName,
Person.Age, Person.Devices);
Redirect('/people');
end;
end.

View File

@ -0,0 +1 @@
[{"first_name":"Daniele","last_name":"Teti","age":43,"devices":[],"guid":"49E8419B66C744529D63DB292389D541"},{"first_name":"Peter","last_name":"Parker","age":23,"devices":[],"guid":"C5489969A04D4AE4B00D4FC50C8ADB5C"},{"first_name":"Bruce","last_name":"Banner","age":50,"devices":[],"guid":"B41D180F30584558B4F4A1AAF849FFA3"},{"first_name":"Sue","last_name":"Storm","age":33,"devices":[],"guid":"3F058118B8C6470D9684E127BC30A84A"},{"first_name":"Scott","last_name":"Summer","age":35,"devices":[],"guid":"3518D8C6F60E42D19C5A7250ADEADC33"},{"first_name":"Reed","last_name":"Richards","age":45,"devices":["smartphone","desktop"],"guid":"09C85C9DEB714476AADB9EB0AD689536"}]

View File

@ -0,0 +1,83 @@
{{include("partials/header.tpro")}}
<script>
function doDelete(id) {
if (confirm('Are you sure?')) {
let form = document.getElementById("myForm");
form.action = "/deleteperson";
form.submit();
}
}
</script>
<div class="row_fluid">
<div class="col-sm-12">
<form class="form form-horizontal" id="myForm" name="myForm" method="POST" action="/people">
<input type="hidden" value="{{person.guid}}" name="guid">
<div class="row">
<div class="col-sm-offset-2 col-sm-8">
{{if(!person)}}
<h3>New Person</h3>
{{else}}
<h3>Edit Person</h3>
{{endif}}
</div>
</div>
<div class="row">
<div class="form-group">
<label for="first_name" class="col-sm-2 control-label">First name</label>
<div class="col-sm-4">
<input type="text" value="{{person.FirstName}}" class="form-control" id="first_name" placeholder="First name" name="first_name">
</div>
</div>
</div>
<div class="row">
<div class="form-group">
<label for="last_name" class="col-sm-2 control-label">Last name</label>
<div class="col-sm-4">
<input type="text" value="{{person.LastName}}" class="form-control" id="last_name" placeholder="Last name" name="last_name">
</div>
</div>
</div>
<div class="row">
<div class="form-group">
<label for="age" class="col-sm-2 control-label">Age</label>
<div class="col-sm-4">
<input type="number" value="{{person.Age}}" class="form-control" id="age" placeholder="Age" name="age">
</div>
</div>
</div>
<div class="row">
<div class="form-group">
<label for="devices" class="col-sm-2 control-label">Devices</label>
<div class="col-sm-4">
<select name="devices" multiple class="form-control">
{{loop(devices)}}
<option value="{{devices.DeviceName}}" {{if(devices.Selected)}}selected{{endif}}>{{devices.DeviceName}}</option>
{{endloop}}
</select>
<span style="font-size: 80%">(Ctrl+Click to select multiple devices)</span>
</div>
</div>
</div>
<div class="form-group">
<div class="col-sm-offset-2 col-sm-2">
<button type="button" class="btn btn-default btn-block" onclick="history.back()">Return to the list</button>
</div>
<div class="col-sm-2">
{{if(person)}}
<button type="button" onclick="doDelete()" class="btn btn-primary btn-block">Delete</button>
{{else}}
<button type="submit" class="btn btn-primary btn-block">Save</button>
{{endif}}
</div>
</div>
</form>
</div>
</div>
{{include("partials/footer.tpro")}}

View File

@ -0,0 +1 @@
{{people.FirstName}}, {{people.LastName}}

View File

@ -1,18 +1,17 @@
<div class="row_fluid">
<div class="row_fluid">
<div class="col-sm-12">
<div style="height: 100px"></div>
</div>
</div>
<div class="row_fluid">
<div class="col-sm-12">
<span>N.B. All these views are UTF-8 without BOM (and this is Unicode Text: "Manutenção")</span>
<span>N.B. All these views are UTF-8 encoded with BOM</span>
</div>
<div class="col-sm-8 bg-primary">
<span>Powered by DMVCFramework</span>
</div>
<div class="col-sm-4 bg-success">
<span>Page generated in with a custom view engine</span>
<span>Server Side Views <a href="/showcase">showcase</a></span>
</div>
</div>

View File

@ -1,17 +1,15 @@
<!DOCTYPE html>
<!DOCTYPE html>
<html>
<header>
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.min.css" integrity="sha384-1q8mTJOASx8j1Au+a5WDVnPi2lkFfwwEAa8hDDdjZlpLegxhjVME1fgjWPGmkzs7" crossorigin="anonymous">
<style>
body {
padding: 20px auto;
padding: 20px 50px 20px 50px;
}
</style>
</header>
<body>
<div id="main" class="container">
<h1>Customer Server Side Views Primer <small>DMVCFramework</small></h1>
<blockquote>
<p>This page uses the TemplatePro engine (it's a sample to show how to use a custom view engine)</p>
</blockquote>
<body>
<div id="main" class="container">
<h1>Server Side Views Primer <small>DMVCFramework</small></h1>

View File

@ -0,0 +1 @@
GUID;FirstName;LastName;Age

View File

@ -0,0 +1,2 @@
{{loop(people)}}{{people.guid}};"{{people.FirstName}}";"{{people.LastName}}";{{people.Age}}
{{endloop}}

View File

@ -0,0 +1,45 @@
{{include("partials/header.tpro")}}
<div class="row_fluid">
<div class="col-sm-12">
<table class="table table-striped">
<thead>
<tr>
<th>#</th>
<th>First name</th>
<th>Last name</th>
<th>Age</th>
<th>&nbsp;</th>
</tr>
</thead>
<tbody>
{{if(people)}}
{{loop(people)}}
<tr>
<td>{{people.@@index}}</td>
<td>{{people.FirstName}}</td>
<td>{{people.LastName}}</td>
<td>{{people.Age}}</td>
<td class="text-right">
{{include("view_person_link.tpro")}}
</td>
</tr>
{{endloop}}
{{else}}
<tr>
<td class="text-center" colspan="5">&lt;&lt;No People Found&gt;&gt;</td>
</tr>
{{endif}}
</tbody>
</table>
</div>
</div>
<br>
<div class="row_fluid">
<div class="col-sm-2">
<a class="btn btn-default btn-block" href="/people/formats/csv">Export as CSV</a>
</div>
<div class="col-sm-2 col-sm-offset-8">
<a class="btn btn-primary btn-block" href="/new">Add New Person</a>
</div>
</div>
{{include("partials/footer.tpro")}}

View File

@ -0,0 +1,4 @@
LIST
{{#people}}
- {{id}};"{{first_name}}";"{{last_name}}";{{age}}
{{/people}}

View File

@ -0,0 +1,135 @@
<!DOCTYPE html>
<html>
<header>
<style>
body {
font-family: Consolas, 'Courier New';
}
blockquote {
font-style: italic;
color: #a0a0a0;
padding: 0.2em;
}
.section {
background-color: #3a3a3a;
color: white;
border-left: 0.5em red solid;
padding: 0.5em;
}
.box {
border: thin black solid;
margin: auto;
width: 80%;
padding: 2em;
}
</style>
</header>
<body>
<h1>TemplatePro Showcase</h1>
<p>
This page is a showcase for all the TemplatePro features usable from DMVCFramework Server Side Views using the default TemplatePro engine.
</p>
<div>
<h2 class="section">List of objects</h2>
<div>
{{if(people)}}
<div>No People Found</div>
{{else}}
{{loop(people)}}
<div>{{people.@@index}}. {{people.FirstName}} {{people.LastName}}</div>
{{endloop}}
{{endif}}
</div>
</div>
<div>
<h2 class="section">Handle empty list of objects</h2>
<div>
{{if(!people2)}}
<div>No People Found</div>
{{else}}
{{loop(people2)}}
<div>{{people2.@@index}}. {{people2.FirstName}} {{people2.LastName}}</div>
{{endloop}}
{{endif}}
</div>
</div>
<div>
<h2 class="section">Avoid HTML automatic escaping using {{{variablename$}} (put a $ after variable name)</h2>
<div class="box">
{{myobj.rawhtml$}}
<br>
<blockquote >Check source code to know how to escape curly braces</blockquote >
</div>
</div>
<div>
<h2 class="section">Handling Include</h2>
<div>
<h2><pre>Using include("partial_person.tpro")</pre></h2>
<ul>
{{loop(people)}}
<li>{{include("partial_person.tpro")}}</li>
{{endloop}}
</ul>
</div>
</div>
<div>
<h2 class="section">Using Filters</h2>
<ul>
<li>Using syntax {{{"value as string"|uppercase}} = {{"value as string"|uppercase}} Filter "uppercase" is invoked passing "value as string" as constant value.</li>
<li>Using syntax {{{first_name!uppercase}} = {{first_name|uppercase}} Filter "uppercase" is invoked passing the value contained in the attribute first_name.</li>
<li>Helpers cannot be nested (yet)</li>
</ul>
<div>
{{loop(people)}}
<div>{{people.@@index}}. {{people.first_name|uppercase}} {{people.last_name|uppercase}}</div>
{{endloop}}
</div>
<hr>
<h3>TemplatePro provides the following built-in helpers</h3>
<p>uppercase</p>
<p>lowercase</p>
<p>capitalize</p>
<p>rpad</p>
<p>lpad</p>
<p>datetostr</p>
<p>datetimetostr</p>
<p>formatdatetime</p>
<hr>
<h3>DMVCFramework provides some extensions to the TemplatePro helpers</h3>
<p>
{{#=<% %>=}} {{#UpperCase "daniele teti"}} <%={{# }}=%> outputs: {{#UpperCase "daniele teti"}}
</p>
<p>
{{#=<% %>=}} {{#LowerCase "DANIELE TETI"}} <%={{# }}=%> outputs: {{#LowerCase "daniele teti"}}
</p>
<p>
{{#=<% %>=}} {{#Capitalize "daniele teti"}} <%={{# }}=%> outputs: {{#Capitalize "daniele teti"}}
</p>
<p>
{{#=<% %>=}} {{#SnakeCase "daniele teti"}} <%={{# }}=%> outputs: {{#SnakeCase "daniele teti"}}
</p>
</div>
<div>
<h2 class="section">Using Project Specific Helpers</h2>
<h3>Any project can define its own custom helpers</h3>
<p>
{{#MyHelper1 "this is a text"}}
</p>
<p>
{{#MyHelper2 "this is another text"}}
</p>
</div>
</body>
</html>

View File

@ -0,0 +1 @@
<a class="btn btn-default" href="/edit/{{people.guid}}"><span class="glyphicon glyphicon-pencil"></span> View</a>

View File

@ -24,6 +24,26 @@
<CfgParent>Base</CfgParent>
<Base>true</Base>
</PropertyGroup>
<PropertyGroup Condition="('$(Platform)'=='iOSDevice64' and '$(Base)'=='true') or '$(Base_iOSDevice64)'!=''">
<Base_iOSDevice64>true</Base_iOSDevice64>
<CfgParent>Base</CfgParent>
<Base>true</Base>
</PropertyGroup>
<PropertyGroup Condition="('$(Platform)'=='iOSSimARM64' and '$(Base)'=='true') or '$(Base_iOSSimARM64)'!=''">
<Base_iOSSimARM64>true</Base_iOSSimARM64>
<CfgParent>Base</CfgParent>
<Base>true</Base>
</PropertyGroup>
<PropertyGroup Condition="('$(Platform)'=='OSX64' and '$(Base)'=='true') or '$(Base_OSX64)'!=''">
<Base_OSX64>true</Base_OSX64>
<CfgParent>Base</CfgParent>
<Base>true</Base>
</PropertyGroup>
<PropertyGroup Condition="('$(Platform)'=='OSXARM64' and '$(Base)'=='true') or '$(Base_OSXARM64)'!=''">
<Base_OSXARM64>true</Base_OSXARM64>
<CfgParent>Base</CfgParent>
<Base>true</Base>
</PropertyGroup>
<PropertyGroup Condition="('$(Platform)'=='Win32' and '$(Base)'=='true') or '$(Base_Win32)'!=''">
<Base_Win32>true</Base_Win32>
<CfgParent>Base</CfgParent>
@ -78,6 +98,26 @@
<BT_BuildType>Debug</BT_BuildType>
<EnabledSysJars>activity-1.7.2.dex.jar;annotation-experimental-1.3.0.dex.jar;annotation-jvm-1.6.0.dex.jar;annotations-13.0.dex.jar;appcompat-1.2.0.dex.jar;appcompat-resources-1.2.0.dex.jar;billing-6.0.1.dex.jar;biometric-1.1.0.dex.jar;browser-1.4.0.dex.jar;cloud-messaging.dex.jar;collection-1.1.0.dex.jar;concurrent-futures-1.1.0.dex.jar;core-1.10.1.dex.jar;core-common-2.2.0.dex.jar;core-ktx-1.10.1.dex.jar;core-runtime-2.2.0.dex.jar;cursoradapter-1.0.0.dex.jar;customview-1.0.0.dex.jar;documentfile-1.0.0.dex.jar;drawerlayout-1.0.0.dex.jar;error_prone_annotations-2.9.0.dex.jar;exifinterface-1.3.6.dex.jar;firebase-annotations-16.2.0.dex.jar;firebase-common-20.3.1.dex.jar;firebase-components-17.1.0.dex.jar;firebase-datatransport-18.1.7.dex.jar;firebase-encoders-17.0.0.dex.jar;firebase-encoders-json-18.0.0.dex.jar;firebase-encoders-proto-16.0.0.dex.jar;firebase-iid-interop-17.1.0.dex.jar;firebase-installations-17.1.3.dex.jar;firebase-installations-interop-17.1.0.dex.jar;firebase-measurement-connector-19.0.0.dex.jar;firebase-messaging-23.1.2.dex.jar;fmx.dex.jar;fragment-1.2.5.dex.jar;google-play-licensing.dex.jar;interpolator-1.0.0.dex.jar;javax.inject-1.dex.jar;kotlin-stdlib-1.8.22.dex.jar;kotlin-stdlib-common-1.8.22.dex.jar;kotlin-stdlib-jdk7-1.8.22.dex.jar;kotlin-stdlib-jdk8-1.8.22.dex.jar;kotlinx-coroutines-android-1.6.4.dex.jar;kotlinx-coroutines-core-jvm-1.6.4.dex.jar;legacy-support-core-utils-1.0.0.dex.jar;lifecycle-common-2.6.1.dex.jar;lifecycle-livedata-2.6.1.dex.jar;lifecycle-livedata-core-2.6.1.dex.jar;lifecycle-runtime-2.6.1.dex.jar;lifecycle-service-2.6.1.dex.jar;lifecycle-viewmodel-2.6.1.dex.jar;lifecycle-viewmodel-savedstate-2.6.1.dex.jar;listenablefuture-1.0.dex.jar;loader-1.0.0.dex.jar;localbroadcastmanager-1.0.0.dex.jar;okio-jvm-3.4.0.dex.jar;play-services-ads-22.2.0.dex.jar;play-services-ads-base-22.2.0.dex.jar;play-services-ads-identifier-18.0.0.dex.jar;play-services-ads-lite-22.2.0.dex.jar;play-services-appset-16.0.1.dex.jar;play-services-base-18.1.0.dex.jar;play-services-basement-18.1.0.dex.jar;play-services-cloud-messaging-17.0.1.dex.jar;play-services-location-21.0.1.dex.jar;play-services-maps-18.1.0.dex.jar;play-services-measurement-base-20.1.2.dex.jar;play-services-measurement-sdk-api-20.1.2.dex.jar;play-services-stats-17.0.2.dex.jar;play-services-tasks-18.0.2.dex.jar;print-1.0.0.dex.jar;profileinstaller-1.3.0.dex.jar;room-common-2.2.5.dex.jar;room-runtime-2.2.5.dex.jar;savedstate-1.2.1.dex.jar;sqlite-2.1.0.dex.jar;sqlite-framework-2.1.0.dex.jar;startup-runtime-1.1.1.dex.jar;tracing-1.0.0.dex.jar;transport-api-3.0.0.dex.jar;transport-backend-cct-3.1.8.dex.jar;transport-runtime-3.1.8.dex.jar;user-messaging-platform-2.0.0.dex.jar;vectordrawable-1.1.0.dex.jar;vectordrawable-animated-1.1.0.dex.jar;versionedparcelable-1.1.1.dex.jar;viewpager-1.0.0.dex.jar;work-runtime-2.7.0.dex.jar</EnabledSysJars>
</PropertyGroup>
<PropertyGroup Condition="'$(Base_iOSDevice64)'!=''">
<VerInfo_Keys>CFBundleName=$(MSBuildProjectName);CFBundleDevelopmentRegion=en;CFBundleDisplayName=$(MSBuildProjectName);CFBundleIdentifier=$(MSBuildProjectName);CFBundleInfoDictionaryVersion=7.1;CFBundleVersion=1.0.0;CFBundleShortVersionString=1.0.0;CFBundlePackageType=APPL;CFBundleSignature=????;LSRequiresIPhoneOS=true;CFBundleAllowMixedLocalizations=YES;CFBundleExecutable=$(MSBuildProjectName);UIDeviceFamily=iPhone &amp; iPad;NSLocationAlwaysUsageDescription=The reason for accessing the location information of the user;NSLocationWhenInUseUsageDescription=The reason for accessing the location information of the user;NSLocationAlwaysAndWhenInUseUsageDescription=The reason for accessing the location information of the user;UIBackgroundModes=;NSContactsUsageDescription=The reason for accessing the contacts;NSPhotoLibraryUsageDescription=The reason for accessing the photo library;NSPhotoLibraryAddUsageDescription=The reason for adding to the photo library;NSCameraUsageDescription=The reason for accessing the camera;NSFaceIDUsageDescription=The reason for accessing the face id;NSMicrophoneUsageDescription=The reason for accessing the microphone;NSSiriUsageDescription=The reason for accessing Siri;ITSAppUsesNonExemptEncryption=false;NSBluetoothAlwaysUsageDescription=The reason for accessing bluetooth;NSBluetoothPeripheralUsageDescription=The reason for accessing bluetooth peripherals;NSCalendarsUsageDescription=The reason for accessing the calendar data;NSRemindersUsageDescription=The reason for accessing the reminders;NSMotionUsageDescription=The reason for accessing the accelerometer;NSSpeechRecognitionUsageDescription=The reason for requesting to send user data to Apple&apos;s speech recognition servers</VerInfo_Keys>
<VerInfo_UIDeviceFamily>iPhoneAndiPad</VerInfo_UIDeviceFamily>
<VerInfo_IncludeVerInfo>true</VerInfo_IncludeVerInfo>
<BT_BuildType>Debug</BT_BuildType>
<VerInfo_BundleId>$(MSBuildProjectName)</VerInfo_BundleId>
</PropertyGroup>
<PropertyGroup Condition="'$(Base_iOSSimARM64)'!=''">
<VerInfo_Keys>CFBundleName=$(MSBuildProjectName);CFBundleDevelopmentRegion=en;CFBundleDisplayName=$(MSBuildProjectName);CFBundleIdentifier=$(MSBuildProjectName);CFBundleInfoDictionaryVersion=7.1;CFBundleVersion=1.0.0;CFBundleShortVersionString=1.0.0;CFBundlePackageType=APPL;CFBundleSignature=????;LSRequiresIPhoneOS=true;CFBundleAllowMixedLocalizations=YES;CFBundleExecutable=$(MSBuildProjectName);UIDeviceFamily=iPhone &amp; iPad;NSLocationAlwaysUsageDescription=The reason for accessing the location information of the user;NSLocationWhenInUseUsageDescription=The reason for accessing the location information of the user;NSLocationAlwaysAndWhenInUseUsageDescription=The reason for accessing the location information of the user;UIBackgroundModes=;NSContactsUsageDescription=The reason for accessing the contacts;NSPhotoLibraryUsageDescription=The reason for accessing the photo library;NSPhotoLibraryAddUsageDescription=The reason for adding to the photo library;NSCameraUsageDescription=The reason for accessing the camera;NSFaceIDUsageDescription=The reason for accessing the face id;NSMicrophoneUsageDescription=The reason for accessing the microphone;NSSiriUsageDescription=The reason for accessing Siri;ITSAppUsesNonExemptEncryption=false;NSBluetoothAlwaysUsageDescription=The reason for accessing bluetooth;NSBluetoothPeripheralUsageDescription=The reason for accessing bluetooth peripherals;NSCalendarsUsageDescription=The reason for accessing the calendar data;NSRemindersUsageDescription=The reason for accessing the reminders;NSMotionUsageDescription=The reason for accessing the accelerometer;NSSpeechRecognitionUsageDescription=The reason for requesting to send user data to Apple&apos;s speech recognition servers</VerInfo_Keys>
<VerInfo_UIDeviceFamily>iPhoneAndiPad</VerInfo_UIDeviceFamily>
<VerInfo_IncludeVerInfo>true</VerInfo_IncludeVerInfo>
</PropertyGroup>
<PropertyGroup Condition="'$(Base_OSX64)'!=''">
<VerInfo_Keys>CFBundleName=$(MSBuildProjectName);CFBundleDisplayName=$(MSBuildProjectName);CFBundleIdentifier=$(MSBuildProjectName);CFBundleVersion=1.0.0;CFBundleShortVersionString=1.0.0;CFBundlePackageType=APPL;CFBundleSignature=????;CFBundleAllowMixedLocalizations=YES;CFBundleExecutable=$(MSBuildProjectName);NSHighResolutionCapable=true;LSApplicationCategoryType=public.app-category.utilities;NSLocationUsageDescription=The reason for accessing the location information of the user;NSContactsUsageDescription=The reason for accessing the contacts;NSCalendarsUsageDescription=The reason for accessing the calendar data;NSRemindersUsageDescription=The reason for accessing the reminders;NSCameraUsageDescription=The reason for accessing the camera;NSMicrophoneUsageDescription=The reason for accessing the microphone;NSMotionUsageDescription=The reason for accessing the accelerometer;NSDesktopFolderUsageDescription=The reason for accessing the Desktop folder;NSDocumentsFolderUsageDescription=The reason for accessing the Documents folder;NSDownloadsFolderUsageDescription=The reason for accessing the Downloads folder;NSNetworkVolumesUsageDescription=The reason for accessing files on a network volume;NSRemovableVolumesUsageDescription=The reason for accessing files on a removable volume;NSSpeechRecognitionUsageDescription=The reason for requesting to send user data to Apple&apos;s speech recognition servers;ITSAppUsesNonExemptEncryption=false;NSBluetoothAlwaysUsageDescription=The reason for accessing the Bluetooth interface</VerInfo_Keys>
<BT_BuildType>Debug</BT_BuildType>
</PropertyGroup>
<PropertyGroup Condition="'$(Base_OSXARM64)'!=''">
<VerInfo_Keys>CFBundleName=$(MSBuildProjectName);CFBundleDisplayName=$(MSBuildProjectName);CFBundleIdentifier=$(MSBuildProjectName);CFBundleVersion=1.0.0;CFBundleShortVersionString=1.0.0;CFBundlePackageType=APPL;CFBundleSignature=????;CFBundleAllowMixedLocalizations=YES;CFBundleExecutable=$(MSBuildProjectName);NSHighResolutionCapable=true;LSApplicationCategoryType=public.app-category.utilities;NSLocationUsageDescription=The reason for accessing the location information of the user;NSContactsUsageDescription=The reason for accessing the contacts;NSCalendarsUsageDescription=The reason for accessing the calendar data;NSRemindersUsageDescription=The reason for accessing the reminders;NSCameraUsageDescription=The reason for accessing the camera;NSMicrophoneUsageDescription=The reason for accessing the microphone;NSMotionUsageDescription=The reason for accessing the accelerometer;NSDesktopFolderUsageDescription=The reason for accessing the Desktop folder;NSDocumentsFolderUsageDescription=The reason for accessing the Documents folder;NSDownloadsFolderUsageDescription=The reason for accessing the Downloads folder;NSNetworkVolumesUsageDescription=The reason for accessing files on a network volume;NSRemovableVolumesUsageDescription=The reason for accessing files on a removable volume;NSSpeechRecognitionUsageDescription=The reason for requesting to send user data to Apple&apos;s speech recognition servers;ITSAppUsesNonExemptEncryption=false;NSBluetoothAlwaysUsageDescription=The reason for accessing the Bluetooth interface</VerInfo_Keys>
<BT_BuildType>Debug</BT_BuildType>
</PropertyGroup>
<PropertyGroup Condition="'$(Base_Win32)'!=''">
<DCC_UsePackage>DBXSqliteDriver;RESTComponents;DataSnapServerMidas;DBXDb2Driver;DBXInterBaseDriver;vclactnband;vclFireDAC;emsclientfiredac;DataSnapFireDAC;svnui;tethering;FireDACADSDriver;DBXMSSQLDriver;DatasnapConnectorsFreePascal;FireDACMSSQLDriver;vcltouch;vcldb;bindcompfmx;svn;DBXOracleDriver;inetdb;FmxTeeUI;emsedge;FireDACIBDriver;fmx;fmxdae;vclib;FireDACDBXDriver;dbexpress;IndyCore;vclx;dsnap;DataSnapCommon;emsclient;FireDACCommon;RESTBackendComponents;DataSnapConnectors;VCLRESTComponents;soapserver;vclie;bindengine;DBXMySQLDriver;FireDACOracleDriver;CloudService;FireDACMySQLDriver;DBXFirebirdDriver;FireDACCommonODBC;FireDACCommonDriver;DataSnapClient;inet;bindcompdbx;IndyIPCommon;vcl;DBXSybaseASEDriver;IndyIPServer;IndySystem;FireDACDb2Driver;dsnapcon;FireDACMSAccDriver;fmxFireDAC;FireDACInfxDriver;vclimg;TeeDB;FireDAC;emshosting;FireDACSqliteDriver;FireDACPgDriver;ibmonitor;FireDACASADriver;DBXOdbcDriver;FireDACTDataDriver;FMXTee;soaprtl;DbxCommonDriver;ibxpress;Tee;DataSnapServer;xmlrtl;soapmidas;DataSnapNativeClient;fmxobj;vclwinx;ibxbindings;rtl;emsserverresource;DbxClientDriver;FireDACDSDriver;DBXSybaseASADriver;CustomIPTransport;vcldsnap;bindcomp;appanalytics;DBXInformixDriver;IndyIPClient;bindcompvcl;TeeUI;dmvcframeworkRT;dbxcds;VclSmp;adortl;FireDACODBCDriver;DataSnapIndy10ServerTransport;dmvcframeworkDT;dsnapxml;DataSnapProviderClient;dbrtl;inetdbxpress;FireDACMongoDBDriver;IndyProtocols;fmxase;$(DCC_UsePackage)</DCC_UsePackage>
<DCC_Namespace>Winapi;System.Win;Data.Win;Datasnap.Win;Web.Win;Soap.Win;Xml.Win;Bde;$(DCC_Namespace)</DCC_Namespace>
@ -1024,7 +1064,11 @@
<Platforms>
<Platform value="Android">False</Platform>
<Platform value="Android64">False</Platform>
<Platform value="iOSDevice64">False</Platform>
<Platform value="iOSSimARM64">False</Platform>
<Platform value="Linux64">False</Platform>
<Platform value="OSX64">False</Platform>
<Platform value="OSXARM64">False</Platform>
<Platform value="Win32">True</Platform>
<Platform value="Win64">False</Platform>
</Platforms>

View File

@ -44,7 +44,7 @@ uses
MVCFramework.Serializer.Defaults,
MVCFramework.Serializer.Intf,
MVCFramework.DuckTyping,
TemplateProU,
TemplatePro,
MVCFramework.Cache,
Data.DB, System.Rtti;
@ -53,12 +53,14 @@ uses
procedure TMVCTemplateProViewEngine.Execute(const ViewName: string;
const OutputStream: TStream);
var
lTP: TTemplateProEngine;
lTP: TTProCompiler;
lViewFileName: string;
lViewTemplate: UTF8String;
lCacheItem: TMVCCacheItem;
lCompiledTemplate: ITProCompiledTemplate;
lPair: TPair<String, TValue>;
begin
lTP := TTemplateProEngine.Create;
lTP := TTProCompiler.Create;
try
lViewFileName := GetRealFileName(ViewName);
if not FileExists(lViewFileName) then
@ -79,16 +81,24 @@ begin
if lCacheItem.TimeStamp < TFile.GetLastWriteTime(lViewFileName) then
begin
lViewTemplate := TFile.ReadAllText(lViewFileName, TEncoding.UTF8);
TMVCCacheSingleton.Instance.SetValue(lViewFileName,
lViewTemplate);
TMVCCacheSingleton.Instance.SetValue(lViewFileName, lViewTemplate);
end;
end;
lViewTemplate := lCacheItem.Value.AsString;
try
lTP.Execute(lViewTemplate, TTPObjectListDictionary(ViewModel), nil, OutputStream);
lCompiledTemplate := lTP.Compile(lViewTemplate, lViewFileName);
if Assigned(ViewModel) then
begin
for lPair in ViewModel do
begin
lCompiledTemplate.SetData(lPair.Key, ViewModel[lPair.Key]);
end;
end;
//lCompiledTemplate.DumpToFile(TPath.Combine(AppPath, 'TProDump.txt'));
TStringStream(OutputStream).WriteString(lCompiledTemplate.Render);
except
on E: EParserException do
on E: ETProException do
begin
raise EMVCViewError.CreateFmt('View [%s] error: %s (%s)',
[ViewName, E.Message, E.ClassName]);

1861
sources/TemplatePro.pas Normal file

File diff suppressed because it is too large Load Diff