New sample: HTMX_mustache, fix for some unit test, workaround for IdCustomHTTP strange behaviour in case of empty response and HTTP 200 OK.

This commit is contained in:
Daniele Teti 2023-09-25 23:55:28 +02:00
parent 4a931c13ad
commit 3d910bdfed
25 changed files with 2067 additions and 176 deletions

View File

@ -373,6 +373,8 @@ resourcestring
' Config[TMVCConfigKey.DefaultViewFileExtension] := dotEnv.Env(''dmvc.default.view_file_extension'', ''html'');' + sLineBreak +
' //view path' + sLineBreak +
' Config[TMVCConfigKey.ViewPath] := dotEnv.Env(''dmvc.view_path'', ''templates'');' + sLineBreak +
' //use cache for server side views (use "false" in debug and "true" in production for faster performances' + sLineBreak +
' Config[TMVCConfigKey.ViewCache] := dotEnv.Env(''dmvc.view_cache'', ''false'');' + sLineBreak +
' //Max Record Count for automatic Entities CRUD' + sLineBreak +
' Config[TMVCConfigKey.MaxEntitiesRecordCount] := dotEnv.Env(''dmvc.max_entities_record_count'', IntToStr(TMVCConstants.MAX_RECORD_COUNT));' + sLineBreak +
' //Enable Server Signature in response' + sLineBreak +

View File

@ -0,0 +1,307 @@
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;
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

@ -0,0 +1,12 @@
object WebModule1: TWebModule1
OnCreate = WebModuleCreate
OnDestroy = WebModuleDestroy
Actions = <
item
Default = True
Name = 'DefaultHandler'
PathInfo = '/'
end>
Height = 230
Width = 415
end

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.Mustache,
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', 'mustache');
//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(TMVCMustacheViewEngine)
end;
procedure TWebModule1.WebModuleDestroy(Sender: TObject);
begin
FMVCEngine.Free;
end;
end.

View File

@ -0,0 +1,245 @@
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 PeopleList: String;
[MVCPath('/search')]
[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;
[MVCPath('/delete/($guid)')]
[MVCHTTPMethods([httpDELETE])]
procedure DeletePerson(const guid: string);
[MVCPath('/new')]
[MVCHTTPMethods([httpGET])]
[MVCProduces(TMVCMediaType.TEXT_HTML)]
procedure NewPerson;
[MVCPath('/edit/($guid)')]
[MVCHTTPMethods([httpGET])]
[MVCProduces(TMVCMediaType.TEXT_HTML)]
function EditPerson(guid: string): String;
[MVCPath]
[MVCHTTPMethods([httpGET])]
[MVCProduces(TMVCMediaType.TEXT_HTML)]
procedure Index;
[MVCPath('/mustacheshowcase')]
[MVCHTTPMethods([httpGET])]
[MVCProduces(TMVCMediaType.TEXT_HTML)]
procedure MustacheTemplateShowCase;
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);
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 := GetRenderedView(['header', 'editperson', 'footer']);
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 := GetRenderedView(['people_header.csv', 'people_list.csv']);
finally
lPeople.Free;
end;
end;
procedure TWebSiteController.Index;
begin
Redirect('/people');
end;
procedure TWebSiteController.MustacheTemplateShowCase;
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;
LoadView(['showcase']);
RenderResponseStream;
finally
lMyObj.Free;
end;
finally
lPeople2.Free;
end;
finally
lPeople.Free;
end;
end;
procedure TWebSiteController.NewPerson;
var
LDAL: IPeopleDAL;
lDevices: TDeviceList;
begin
LDAL := TServicesFactory.GetPeopleDAL;
lDevices := LDAL.GetDevicesList;
try
ViewData['deviceslist'] := lDevices;
LoadView(['header', 'editperson', 'footer']);
RenderResponseStream;
finally
lDevices.Free;
end;
end;
function TWebSiteController.PeopleList: String;
var
LDAL: IPeopleDAL;
lPeople: TPeople;
begin
LDAL := TServicesFactory.GetPeopleDAL;
lPeople := LDAL.GetPeople;
try
ViewData['people'] := lPeople;
Result := GetRenderedView(['header', 'people_list_search', 'people_list', 'people_list_bottom', 'footer']);
finally
lPeople.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;
if Context.Request.IsHTMX then
begin
Result := GetRenderedView(['people_list']);
if SearchText.IsEmpty then
Context.Response.SetPushUrl('/people/search')
else
Context.Response.SetPushUrl('/people/search?q=' + SearchText);
end
else
begin
var lJSON := TJSONObject.Create;
try
lJSON.S['q'] := SearchText;
Result := GetRenderedView(['header', 'people_list_search', 'people_list', 'people_list_bottom', 'footer'], lJSON);
finally
lJSON.Free;
end;
end;
finally
lPeople.Free;
end;
end;
procedure TWebSiteController.SavePerson;
var
LFirstName: string;
LLastName: string;
LAge: string;
LPeopleDAL: IPeopleDAL;
lDevices: TArray<string>;
begin
LFirstName := Context.Request.Params['first_name'].Trim;
LLastName := Context.Request.Params['last_name'].Trim;
LAge := Context.Request.Params['age'];
lDevices := Context.Request.ParamsMulti['items'];
if LFirstName.IsEmpty or LLastName.IsEmpty or LAge.IsEmpty 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(LFirstName, LLastName, LAge.ToInteger(), lDevices);
Context.Response.SetRedirect('/people');
end;
end.

View File

@ -0,0 +1 @@
<a hx-delete="/people/delete/{{guid}}" hx-confirm="Are you sure to delete {{first_name}} {{last_name}} ?" href="" hx-swap="delete" hx-target="closest tr">Delete</a>

View File

@ -0,0 +1,81 @@
<script>
function doDelete(id) {
if (confirm('Are you sure?')) {
let form = document.getElementById("myForm");
form.action = "/deleteperson";
form.submit();
}
}
</script>
<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">
{{^person}}
<h3>New Person</h3>
{{/person}} {{#person}}
<h3>Edit Person</h3>
{{/person}}
</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.first_name}}" class="form-control" id="first_name" placeholder="First name" name="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.last_name}}" 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 name="items" multiple class="form-control">
{{#deviceslist}}
<option value="{{devicename}}" {{#selected}}selected{{/selected}}>{{devicename}}</option>
{{/deviceslist}}
</select>
<span style="font-size: 80%">(Ctrl+Click to select multiple devices)</span>
</div>
</div>
</div>
<div class="row">
<div class="col">
<button type="button" class="btn btn-default btn-block" onclick="history.back()">Return to the list</button>
</div>
<div class="offset-5">
</div>
<div class="col">
{{#person}}
<button
type="button"
hx-confirm="Are you sure you wish to delete user?"
hx-delete="/people/delete/{{#person}}{{guid}}{{/person}}"
class="btn btn-warning btn-block">
Delete
</button>
{{/person}}
</div>
<div class="col">
<button type="submit" class="btn btn-primary btn-block">Save</button>
</div>
</div>
</form>

View File

@ -0,0 +1,24 @@
<div class="row_fluid">
<div class="col-sm">
<div style="height: 100px"></div>
</div>
</div>
<div class="row_fluid">
<div class="col-sm">
<span>N.B. All these views are UTF-8 encoded with BOM</span>
</div>
</div>
<div class="row">
<div class="col-sm bg-primary">
<span>Powered by DMVCFramework</span>
</div>
<div class="col-sm bg-warning">
<span>Server Side Views <a href="/people/mustacheshowcase">showcase</a></span>
</div>
</div>
</div>
</body>
</html>

View File

@ -0,0 +1,17 @@
<!DOCTYPE html>
<html>
<header>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@4.0.0/dist/css/bootstrap.min.css" integrity="sha384-Gn5384xqQ1aoWXA+058RXPxPg6fy4IWvTNh0E263XmFcJlSAwiGgFAW/dAiS6JXm" crossorigin="anonymous">
<script src="https://unpkg.com/htmx.org@1.9.6" integrity="sha384-FhXw7b6AlE/jyjlZH5iHa/tTe9EpJ1Y55RjcgPbjeWMskSxZt1v9qkxLJWNJaGni" crossorigin="anonymous"></script>
<style>
body {
padding: 20px 50px 20px 50px;
}
</style>
</header>
<body>
<div id="main" class="container">
<div class="shadow-sm p-3 mb-5 bg-body-tertiary rounded">
<h1>HTMX Sample <small>DMVCFramework</small></h1>
</div>

View File

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

View File

@ -0,0 +1 @@
guid;first_name;last_name;age

View File

@ -0,0 +1,2 @@
{{#people}}{{guid}};"{{first_name}}";"{{last_name}}";{{age}}
{{/people}}

View File

@ -0,0 +1,5 @@
<div class="row_fluid" id="people_list">
<div class="col-sm-12">
{{>people_table}}
</div>
</div>

View File

@ -0,0 +1,8 @@
<div class="row">
<div class="offset-7 col-2">
<a href="/people/exports/csv" download="people.csv" target="_blank">Export as CSV</a>
</div>
<div class="col-2">
<a hx-boost="true" href="/people/new">Add New Person</a>
</div>
</div>

View File

@ -0,0 +1,13 @@
<div class="row_fluid">
<div class="offset-sm-6 col-sm-6 text-right">
<form hx-get="/people/search" 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,26 @@
<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>
{{^people}}
<tr>
<td class="text-center" colspan="5">&lt;&lt;No People Found&gt;&gt;</td>
</tr>
{{/people}} {{#people}}
<tr>
<td>{{-index}}</td>
<td>{{first_name}}</td>
<td>{{last_name}}</td>
<td>{{age}}</td>
<td class="text-right">{{>delete_person_link}} | {{>view_person_link}}</td>
</tr>
{{/people}}
</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/{{guid}}">View</a>

View File

@ -0,0 +1,86 @@
program htmx_mustache;
{$APPTYPE CONSOLE}
uses
System.SysUtils,
MVCFramework,
MVCFramework.Signal,
MVCFramework.Logger,
{$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.HTMX in '..\htmx\MVCFramework.HTMX.pas', MVCFramework.Commons,
MVCFramework.DotEnv;
{$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;
{$IFDEF MSWINDOWS}
ShellExecute(0, 'open', 'http://localhost:8080', nil, nil, SW_SHOW);
{$ENDIF}
Write('Ctrl+C to stop the server');
WaitForTerminationSignal;
EnterInShutdownState;
LServer.Active := False;
finally
LServer.Free;
end;
end;
begin
{ Enable ReportMemoryLeaksOnShutdown during debug }
// ReportMemoryLeaksOnShutdown := True;
IsMultiThread := 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;
dotEnvConfigure(
function: IMVCDotEnv
begin
Result := NewDotEnv
.WithStrategy(TMVCDotEnvPriority.FileThenEnv)
//if available, by default, loads default environment (.env)
.UseProfile('test') //if available loads the test environment (.env.test)
.UseProfile('prod') //if available loads the prod environment (.env.prod)
.UseLogger(procedure(LogItem: String)
begin
LogW('dotEnv: ' + LogItem);
end)
.Build(); //uses the executable folder to look for .env* files
end);
WebRequestHandlerProc.MaxConnections := dotEnv.Env('dmvc.handler.max_connections', 1024);
RunServer(dotEnv.Env('dmvc.server.port', 8080));
except
on E: Exception do
Writeln(E.ClassName, ': ', E.Message);
end;
end.

View File

@ -0,0 +1,869 @@
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<ProjectGuid>{563921D8-AB80-4E14-AD4E-36870C7E2008}</ProjectGuid>
<ProjectVersion>19.5</ProjectVersion>
<FrameworkType>VCL</FrameworkType>
<MainSource>htmx_mustache.dpr</MainSource>
<Base>True</Base>
<Config Condition="'$(Config)'==''">Debug</Config>
<Platform Condition="'$(Platform)'==''">Win32</Platform>
<TargetedPlatforms>1</TargetedPlatforms>
<AppType>Console</AppType>
</PropertyGroup>
<PropertyGroup Condition="'$(Config)'=='Base' or '$(Base)'!=''">
<Base>true</Base>
</PropertyGroup>
<PropertyGroup Condition="('$(Platform)'=='Win32' and '$(Base)'=='true') or '$(Base_Win32)'!=''">
<Base_Win32>true</Base_Win32>
<CfgParent>Base</CfgParent>
<Base>true</Base>
</PropertyGroup>
<PropertyGroup Condition="('$(Platform)'=='Win64' and '$(Base)'=='true') or '$(Base_Win64)'!=''">
<Base_Win64>true</Base_Win64>
<CfgParent>Base</CfgParent>
<Base>true</Base>
</PropertyGroup>
<PropertyGroup Condition="'$(Config)'=='Debug' or '$(Cfg_1)'!=''">
<Cfg_1>true</Cfg_1>
<CfgParent>Base</CfgParent>
<Base>true</Base>
</PropertyGroup>
<PropertyGroup Condition="('$(Platform)'=='Win32' and '$(Cfg_1)'=='true') or '$(Cfg_1_Win32)'!=''">
<Cfg_1_Win32>true</Cfg_1_Win32>
<CfgParent>Cfg_1</CfgParent>
<Cfg_1>true</Cfg_1>
<Base>true</Base>
</PropertyGroup>
<PropertyGroup Condition="'$(Config)'=='Release' or '$(Cfg_2)'!=''">
<Cfg_2>true</Cfg_2>
<CfgParent>Base</CfgParent>
<Base>true</Base>
</PropertyGroup>
<PropertyGroup Condition="'$(Base)'!=''">
<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>htmx_mustache</SanitizedProjectName>
<DCC_DcuOutput>.\$(Platform)\$(Config)</DCC_DcuOutput>
<DCC_ExeOutput>.\$(Platform)\$(Config)</DCC_ExeOutput>
<DCC_E>false</DCC_E>
<DCC_N>false</DCC_N>
<DCC_S>false</DCC_S>
<DCC_F>false</DCC_F>
<DCC_K>false</DCC_K>
</PropertyGroup>
<PropertyGroup Condition="'$(Base_Win32)'!=''">
<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.$(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>
<PropertyGroup Condition="'$(Base_Win64)'!=''">
<DCC_UsePackage>DBXSqliteDriver;RESTComponents;DataSnapServerMidas;DBXDb2Driver;DBXInterBaseDriver;vclactnband;vclFireDAC;emsclientfiredac;DataSnapFireDAC;tethering;FireDACADSDriver;DBXMSSQLDriver;DatasnapConnectorsFreePascal;FireDACMSSQLDriver;vcltouch;vcldb;bindcompfmx;Intraweb;DBXOracleDriver;inetdb;FmxTeeUI;FireDACIBDriver;fmx;fmxdae;vclib;FireDACDBXDriver;dbexpress;IndyCore;vclx;dsnap;DataSnapCommon;emsclient;FireDACCommon;RESTBackendComponents;DataSnapConnectors;VCLRESTComponents;soapserver;vclie;bindengine;DBXMySQLDriver;FireDACOracleDriver;CloudService;FireDACMySQLDriver;DBXFirebirdDriver;FireDACCommonDriver;DataSnapClient;inet;bindcompdbx;IndyIPCommon;vcl;DBXSybaseASEDriver;IndyIPServer;IndySystem;FireDACDb2Driver;dsnapcon;FireDACMSAccDriver;fmxFireDAC;FireDACInfxDriver;vclimg;TeeDB;FireDAC;FireDACSqliteDriver;FireDACPgDriver;ibmonitor;FireDACASADriver;DBXOdbcDriver;FireDACTDataDriver;FMXTee;ipstudiowinclient;soaprtl;DbxCommonDriver;ibxpress;Tee;DataSnapServer;xmlrtl;soapmidas;DataSnapNativeClient;fmxobj;vclwinx;ibxbindings;rtl;FireDACDSDriver;DbxClientDriver;DBXSybaseASADriver;CustomIPTransport;vcldsnap;bindcomp;appanalytics;ipstudiowin;DBXInformixDriver;officeXPrt;IndyIPClient;bindcompvcl;TeeUI;vclribbon;dbxcds;VclSmp;adortl;FireDACODBCDriver;DataSnapIndy10ServerTransport;dsnapxml;DataSnapProviderClient;dbrtl;inetdbxpress;FireDACMongoDBDriver;IndyProtocols;fmxase;$(DCC_UsePackage)</DCC_UsePackage>
</PropertyGroup>
<PropertyGroup Condition="'$(Cfg_1)'!=''">
<DCC_Define>DEBUG;$(DCC_Define)</DCC_Define>
<DCC_DebugDCUs>true</DCC_DebugDCUs>
<DCC_Optimize>false</DCC_Optimize>
<DCC_GenerateStackFrames>true</DCC_GenerateStackFrames>
<DCC_DebugInfoInExe>true</DCC_DebugInfoInExe>
<DCC_RemoteDebug>true</DCC_RemoteDebug>
</PropertyGroup>
<PropertyGroup Condition="'$(Cfg_1_Win32)'!=''">
<VerInfo_Locale>1033</VerInfo_Locale>
<Manifest_File>None</Manifest_File>
<DCC_RemoteDebug>false</DCC_RemoteDebug>
</PropertyGroup>
<PropertyGroup Condition="'$(Cfg_2)'!=''">
<DCC_LocalDebugSymbols>false</DCC_LocalDebugSymbols>
<DCC_Define>RELEASE;$(DCC_Define)</DCC_Define>
<DCC_SymbolReferenceInfo>0</DCC_SymbolReferenceInfo>
<DCC_DebugInformation>0</DCC_DebugInformation>
</PropertyGroup>
<ItemGroup>
<DelphiCompile Include="$(MainSource)">
<MainSource>MainSource</MainSource>
</DelphiCompile>
<DCCReference Include="WebModuleU.pas">
<Form>WebModule1</Form>
<DesignClass>TWebModule</DesignClass>
</DCCReference>
<DCCReference Include="WebSiteControllerU.pas"/>
<DCCReference Include="DAL.pas"/>
<DCCReference Include="..\renders\MyDataModuleU.pas">
<Form>MyDataModule</Form>
<DesignClass>TDataModule</DesignClass>
</DCCReference>
<DCCReference Include="..\htmx\MVCFramework.HTMX.pas"/>
<BuildConfiguration Include="Base">
<Key>Base</Key>
</BuildConfiguration>
<BuildConfiguration Include="Debug">
<Key>Cfg_1</Key>
<CfgParent>Base</CfgParent>
</BuildConfiguration>
<BuildConfiguration Include="Release">
<Key>Cfg_2</Key>
<CfgParent>Base</CfgParent>
</BuildConfiguration>
</ItemGroup>
<ProjectExtensions>
<Borland.Personality>Delphi.Personality.12</Borland.Personality>
<Borland.ProjectType>Console</Borland.ProjectType>
<BorlandProject>
<Delphi.Personality>
<Source>
<Source Name="MainSource">htmx_mustache.dpr</Source>
</Source>
<Excluded_Packages>
<Excluded_Packages Name="$(BDSBIN)\bcboffice2k230.bpl">Embarcadero C++Builder Office 2000 Servers Package</Excluded_Packages>
<Excluded_Packages Name="$(BDSBIN)\bcbofficexp230.bpl">Embarcadero C++Builder Office XP Servers Package</Excluded_Packages>
<Excluded_Packages Name="$(BDSBIN)\dcloffice2k230.bpl">Microsoft Office 2000 Sample Automation Server Wrapper Components</Excluded_Packages>
</Excluded_Packages>
</Delphi.Personality>
<Deployment Version="4">
<DeployFile LocalName="bin\ServerSideViews.exe" Configuration="Debug" Class="ProjectOutput"/>
<DeployFile LocalName="bin\htmx_mustache.exe" Configuration="Debug" Class="ProjectOutput">
<Platform Name="Win32">
<RemoteName>htmx_mustache.exe</RemoteName>
<Overwrite>true</Overwrite>
</Platform>
</DeployFile>
<DeployClass Name="AdditionalDebugSymbols">
<Platform Name="OSX32">
<Operation>1</Operation>
</Platform>
<Platform Name="Win32">
<Operation>0</Operation>
</Platform>
</DeployClass>
<DeployClass Name="AndroidClasses">
<Platform Name="Android">
<RemoteDir>classes</RemoteDir>
<Operation>64</Operation>
</Platform>
<Platform Name="Android64">
<RemoteDir>classes</RemoteDir>
<Operation>64</Operation>
</Platform>
</DeployClass>
<DeployClass Name="AndroidFileProvider">
<Platform Name="Android">
<RemoteDir>res\xml</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="Android64">
<RemoteDir>res\xml</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="AndroidGDBServer">
<Platform Name="Android">
<RemoteDir>library\lib\armeabi-v7a</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="AndroidLibnativeArmeabiFile">
<Platform Name="Android">
<RemoteDir>library\lib\armeabi</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="Android64">
<RemoteDir>library\lib\armeabi</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="AndroidLibnativeArmeabiv7aFile">
<Platform Name="Android64">
<RemoteDir>library\lib\armeabi-v7a</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="AndroidLibnativeMipsFile">
<Platform Name="Android">
<RemoteDir>library\lib\mips</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="Android64">
<RemoteDir>library\lib\mips</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="AndroidServiceOutput">
<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>
</DeployClass>
<DeployClass Name="AndroidServiceOutput_Android32">
<Platform Name="Android64">
<RemoteDir>library\lib\armeabi-v7a</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="AndroidSplashImageDef">
<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="AndroidSplashStyles">
<Platform Name="Android">
<RemoteDir>res\values</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="Android64">
<RemoteDir>res\values</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="AndroidSplashStylesV21">
<Platform Name="Android">
<RemoteDir>res\values-v21</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="Android64">
<RemoteDir>res\values-v21</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="Android_Colors">
<Platform Name="Android">
<RemoteDir>res\values</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="Android64">
<RemoteDir>res\values</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="Android_DefaultAppIcon">
<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_LauncherIcon144">
<Platform Name="Android">
<RemoteDir>res\drawable-xxhdpi</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="Android64">
<RemoteDir>res\drawable-xxhdpi</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="Android_LauncherIcon192">
<Platform Name="Android">
<RemoteDir>res\drawable-xxxhdpi</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="Android64">
<RemoteDir>res\drawable-xxxhdpi</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="Android_LauncherIcon36">
<Platform Name="Android">
<RemoteDir>res\drawable-ldpi</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="Android64">
<RemoteDir>res\drawable-ldpi</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="Android_LauncherIcon48">
<Platform Name="Android">
<RemoteDir>res\drawable-mdpi</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="Android64">
<RemoteDir>res\drawable-mdpi</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="Android_LauncherIcon72">
<Platform Name="Android">
<RemoteDir>res\drawable-hdpi</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="Android64">
<RemoteDir>res\drawable-hdpi</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="Android_LauncherIcon96">
<Platform Name="Android">
<RemoteDir>res\drawable-xhdpi</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="Android64">
<RemoteDir>res\drawable-xhdpi</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="Android_NotificationIcon24">
<Platform Name="Android">
<RemoteDir>res\drawable-mdpi</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="Android64">
<RemoteDir>res\drawable-mdpi</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="Android_NotificationIcon36">
<Platform Name="Android">
<RemoteDir>res\drawable-hdpi</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="Android64">
<RemoteDir>res\drawable-hdpi</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="Android_NotificationIcon48">
<Platform Name="Android">
<RemoteDir>res\drawable-xhdpi</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="Android64">
<RemoteDir>res\drawable-xhdpi</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="Android_NotificationIcon72">
<Platform Name="Android">
<RemoteDir>res\drawable-xxhdpi</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="Android64">
<RemoteDir>res\drawable-xxhdpi</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="Android_NotificationIcon96">
<Platform Name="Android">
<RemoteDir>res\drawable-xxxhdpi</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="Android64">
<RemoteDir>res\drawable-xxxhdpi</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="Android_SplashImage426">
<Platform Name="Android">
<RemoteDir>res\drawable-small</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="Android64">
<RemoteDir>res\drawable-small</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="Android_SplashImage470">
<Platform Name="Android">
<RemoteDir>res\drawable-normal</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="Android64">
<RemoteDir>res\drawable-normal</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="Android_SplashImage640">
<Platform Name="Android">
<RemoteDir>res\drawable-large</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="Android64">
<RemoteDir>res\drawable-large</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="Android_SplashImage960">
<Platform Name="Android">
<RemoteDir>res\drawable-xlarge</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="Android64">
<RemoteDir>res\drawable-xlarge</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="Android_Strings">
<Platform Name="Android">
<RemoteDir>res\values</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="Android64">
<RemoteDir>res\values</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="DebugSymbols">
<Platform Name="iOSSimulator">
<Operation>1</Operation>
</Platform>
<Platform Name="OSX32">
<Operation>1</Operation>
</Platform>
<Platform Name="Win32">
<Operation>0</Operation>
</Platform>
</DeployClass>
<DeployClass Name="DependencyFramework">
<Platform Name="OSX32">
<Operation>1</Operation>
<Extensions>.framework</Extensions>
</Platform>
<Platform Name="OSX64">
<Operation>1</Operation>
<Extensions>.framework</Extensions>
</Platform>
<Platform Name="OSXARM64">
<Operation>1</Operation>
<Extensions>.framework</Extensions>
</Platform>
<Platform Name="Win32">
<Operation>0</Operation>
</Platform>
</DeployClass>
<DeployClass Name="DependencyModule">
<Platform Name="OSX32">
<Operation>1</Operation>
<Extensions>.dylib</Extensions>
</Platform>
<Platform Name="OSX64">
<Operation>1</Operation>
<Extensions>.dylib</Extensions>
</Platform>
<Platform Name="OSXARM64">
<Operation>1</Operation>
<Extensions>.dylib</Extensions>
</Platform>
<Platform Name="Win32">
<Operation>0</Operation>
<Extensions>.dll;.bpl</Extensions>
</Platform>
</DeployClass>
<DeployClass Required="true" Name="DependencyPackage">
<Platform Name="iOSDevice32">
<Operation>1</Operation>
<Extensions>.dylib</Extensions>
</Platform>
<Platform Name="iOSDevice64">
<Operation>1</Operation>
<Extensions>.dylib</Extensions>
</Platform>
<Platform Name="iOSSimARM64">
<Operation>1</Operation>
<Extensions>.dylib</Extensions>
</Platform>
<Platform Name="OSX32">
<Operation>1</Operation>
<Extensions>.dylib</Extensions>
</Platform>
<Platform Name="OSX64">
<Operation>1</Operation>
<Extensions>.dylib</Extensions>
</Platform>
<Platform Name="OSXARM64">
<Operation>1</Operation>
<Extensions>.dylib</Extensions>
</Platform>
<Platform Name="Win32">
<Operation>0</Operation>
<Extensions>.bpl</Extensions>
</Platform>
</DeployClass>
<DeployClass Name="File">
<Platform Name="Android">
<Operation>0</Operation>
</Platform>
<Platform Name="Android64">
<Operation>0</Operation>
</Platform>
<Platform Name="iOSDevice32">
<Operation>0</Operation>
</Platform>
<Platform Name="iOSDevice64">
<Operation>0</Operation>
</Platform>
<Platform Name="iOSSimARM64">
<Operation>0</Operation>
</Platform>
<Platform Name="OSX32">
<Operation>0</Operation>
</Platform>
<Platform Name="OSX64">
<Operation>0</Operation>
</Platform>
<Platform Name="OSXARM64">
<Operation>0</Operation>
</Platform>
<Platform Name="Win32">
<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>
</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>
<Operation>1</Operation>
</Platform>
<Platform Name="iOSSimARM64">
<RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="iPad_AppIcon152">
<Platform Name="iOSDevice64">
<RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="iOSSimARM64">
<RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="iPad_AppIcon167">
<Platform Name="iOSDevice64">
<RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="iOSSimARM64">
<RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="iPad_Launch2x">
<Platform Name="iOSDevice64">
<RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="iOSSimARM64">
<RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="iPad_LaunchDark2x">
<Platform Name="iOSDevice64">
<RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="iOSSimARM64">
<RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="iPad_Notification40">
<Platform Name="iOSDevice64">
<RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="iOSSimARM64">
<RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="iPad_Setting58">
<Platform Name="iOSDevice64">
<RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="iOSSimARM64">
<RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="iPad_SpotLight80">
<Platform Name="iOSDevice64">
<RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="iOSSimARM64">
<RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="iPhone_AppIcon120">
<Platform Name="iOSDevice64">
<RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="iOSSimARM64">
<RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="iPhone_AppIcon180">
<Platform Name="iOSDevice64">
<RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="iOSSimARM64">
<RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="iPhone_Launch2x">
<Platform Name="iOSDevice64">
<RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="iOSSimARM64">
<RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="iPhone_Launch3x">
<Platform Name="iOSDevice64">
<RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="iOSSimARM64">
<RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="iPhone_LaunchDark2x">
<Platform Name="iOSDevice64">
<RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="iOSSimARM64">
<RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="iPhone_LaunchDark3x">
<Platform Name="iOSDevice64">
<RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="iOSSimARM64">
<RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="iPhone_Notification40">
<Platform Name="iOSDevice64">
<RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="iOSSimARM64">
<RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="iPhone_Notification60">
<Platform Name="iOSDevice64">
<RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="iOSSimARM64">
<RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="iPhone_Setting58">
<Platform Name="iOSDevice64">
<RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="iOSSimARM64">
<RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="iPhone_Setting87">
<Platform Name="iOSDevice64">
<RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="iOSSimARM64">
<RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="iPhone_Spotlight120">
<Platform Name="iOSDevice64">
<RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="iOSSimARM64">
<RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="iPhone_Spotlight80">
<Platform Name="iOSDevice64">
<RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="iOSSimARM64">
<RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<ProjectRoot Platform="Android" Name="$(PROJECTNAME)"/>
<ProjectRoot Platform="Android64" Name="$(PROJECTNAME)"/>
<ProjectRoot Platform="iOSDevice32" Name="$(PROJECTNAME).app"/>
<ProjectRoot Platform="iOSDevice64" Name="$(PROJECTNAME).app"/>
<ProjectRoot Platform="iOSSimARM64" Name="$(PROJECTNAME).app"/>
<ProjectRoot Platform="iOSSimulator" Name="$(PROJECTNAME).app"/>
<ProjectRoot Platform="Linux64" Name="$(PROJECTNAME)"/>
<ProjectRoot Platform="OSX32" Name="$(PROJECTNAME)"/>
<ProjectRoot Platform="OSX64" Name="$(PROJECTNAME)"/>
<ProjectRoot Platform="OSXARM64" Name="$(PROJECTNAME)"/>
<ProjectRoot Platform="Win32" Name="$(PROJECTNAME)"/>
<ProjectRoot Platform="Win64" Name="$(PROJECTNAME)"/>
</Deployment>
<Platforms>
<Platform value="Win32">True</Platform>
<Platform value="Win64">False</Platform>
</Platforms>
</BorlandProject>
<ProjectFileVersion>12</ProjectFileVersion>
</ProjectExtensions>
<Import Project="$(BDS)\Bin\CodeGear.Delphi.Targets" Condition="Exists('$(BDS)\Bin\CodeGear.Delphi.Targets')"/>
<Import Project="$(APPDATA)\Embarcadero\$(BDSAPPDATABASEDIR)\$(PRODUCTVERSION)\UserTools.proj" Condition="Exists('$(APPDATA)\Embarcadero\$(BDSAPPDATABASEDIR)\$(PRODUCTVERSION)\UserTools.proj')"/>
<Import Project="$(MSBuildProjectName).deployproj" Condition="Exists('$(MSBuildProjectName).deployproj')"/>
</Project>

View File

@ -515,6 +515,127 @@
<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>
</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>
@ -715,127 +836,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"/>

View File

@ -150,6 +150,7 @@ type
public const
SessionTimeout = 'sessiontimeout';
ViewPath = 'view_path';
ViewCache = 'view_cache';
DefaultContentType = 'default_content_type';
DefaultContentCharset = 'default_content_charset';
DefaultViewFileExtension = 'default_view_file_extension';
@ -1054,7 +1055,8 @@ begin
if FConfig.ContainsKey(AIndex) then
Result := FConfig.Items[AIndex]
else
raise EMVCConfigException.CreateFmt('Invalid config key [%s]', [AIndex]);
Result := ''; //v3.4.1-sodium (an invalid config key returns empty string, doesn't raise an exception)
//raise EMVCConfigException.CreateFmt('Invalid config key [%s]', [AIndex]);
end;
function TMVCConfig.GetValueAsInt64(const AIndex: string): Int64;

View File

@ -60,7 +60,7 @@ uses
MVCFramework.Serializer.Intf,
MVCFramework.DuckTyping,
MVCFramework.Serializer.JsonDataObjects.OptionalCustomTypes,
MVCFramework.Serializer.JsonDataObjects;
MVCFramework.Serializer.JsonDataObjects, MVCFramework.Serializer.Commons;
{$WARNINGS OFF}
@ -111,6 +111,7 @@ procedure TMVCMustacheViewEngine.LoadPartials;
var
lViewsExtension: string;
lViewPath: string;
lPartialName: String;
lPartialFileNames: TArray<string>;
I: Integer;
begin
@ -126,13 +127,18 @@ begin
begin
lViewsExtension := Config[TMVCConfigKey.DefaultViewFileExtension];
lViewPath := Config[TMVCConfigKey.ViewPath];
lPartialFileNames := TDirectory.GetFiles(lViewPath, '*.' + lViewsExtension);
lPartialFileNames := TDirectory.GetFiles(lViewPath, '*.' + lViewsExtension, TSearchOption.soAllDirectories);
FreeAndNil(fPartials);
fPartials := TSynMustachePartials.Create;
for I := 0 to High(lPartialFileNames) do
begin
fPartials.Add(TPath.GetFileNameWithoutExtension(lPartialFileNames[i]), TFile.ReadAllText(lPartialFileNames[i]));
lPartialName := lPartialFileNames[i]
.Remove(lPartialFileNames[i].Length - lViewsExtension.Length - 1)
.Replace(TPath.DirectorySeparatorChar, '/');
lPartialName := lPartialName.Remove(0, lViewPath.Length + 1);
fPartials.Add(lPartialName, TFile.ReadAllText(lPartialFileNames[i]));
end;
gPartialsLoaded := True;
gPartialsLoaded := SameText(Config[TMVCConfigKey.ViewCache], 'true');
end;
finally
TMonitor.Exit(gLock);
@ -145,43 +151,44 @@ end;
procedure TMVCMustacheViewEngine.PrepareModels;
var
lFirst: Boolean;
lList: IMVCList;
DataObj: TPair<string, TObject>;
lDSPair: TPair<string, TDataSet>;
lSJSON: string;
lJSON: string;
lSer: IMVCSerializer;
lSer: TMVCJsonDataObjectsSerializer;
lJSONModel: TJsonObject;
begin
if Assigned(FJSONModel) then
if Assigned(FJSONModel) and (not Assigned(ViewModel)) and (not Assigned(ViewDataSets)) then
begin
// if only jsonmodel is <> nil then we take the "fast path"
FJSONModelAsString := FJSONModel.ToJSON(False);
Exit;
end;
{TODO -oDanieleT -cGeneral : Quite inefficient to generate JSON in this way. Why don't use a JSONObject directly?}
if (FJSONModelAsString <> '{}') and (not FJSONModelAsString.IsEmpty) then
Exit;
FJSONModelAsString := '{}';
lSer := TMVCJsonDataObjectsSerializer.Create;
try
RegisterOptionalCustomTypesSerializers(lSer);
lSJSON := '{';
lFirst := True;
if Assigned(FJSONModel) then
begin
lJSONModel := FJSONModel.Clone as TJsonObject;
end
else
begin
lJSONModel := TJsonObject.Create;
end;
try
if Assigned(ViewModel) then
begin
for DataObj in ViewModel do
begin
lList := TDuckTypedList.Wrap(DataObj.Value);
if lList <> nil then
lJSON := lSer.SerializeCollection(DataObj.Value)
begin
lSer.ListToJsonArray(lList, lJSONModel.A[DataObj.Key], TMVCSerializationType.stProperties, nil);
end
else
lJSON := lSer.SerializeObject(DataObj.Value);
if not lFirst then
lSJSON := lSJSON + ',';
lSJSON := lSJSON + '"' + DataObj.Key + '":' + lJSON;
lFirst := False;
begin
lSer.ObjectToJsonObject(DataObj.Value, lJSONModel.O[DataObj.Key], TMVCSerializationType.stProperties, nil);
end;
end;
end;
@ -189,16 +196,16 @@ begin
begin
for lDSPair in ViewDataSets do
begin
lJSON := lSer.SerializeDataSet(lDSPair.Value);
if not lFirst then
lSJSON := lSJSON + ',';
lSJSON := lSJSON + '"' + lDSPair.Key + '":' + lJSON;
lFirst := False;
lSer.DataSetToJsonArray(lDSPair.Value, lJSONModel.A[lDSPair.Key], TMVCNameCase.ncAsIs, nil);
end;
end;
lSJSON := lSJSON + '}';
FJSONModelAsString := lSJSON;
FJSONModelAsString := lJSONModel.ToJSON(False);
finally
lJSONModel.Free;
end;
finally
lSer.Free;
end;
end;
end.

View File

@ -1170,7 +1170,11 @@ type
const AViewModel: TMVCViewDataObject;
const AViewDataSets: TObjectDictionary<string, TDataSet>;
const AContentType: string); overload; virtual;
constructor Create(const AEngine: TMVCEngine; const AWebContext: TWebContext;
constructor Create(
const AEngine: TMVCEngine;
const AWebContext: TWebContext;
const AViewModel: TMVCViewDataObject;
const AViewDataSets: TObjectDictionary<string, TDataSet>;
const AJSONModel: TJSONObject;
const AContentType: string); overload; virtual;
destructor Destroy; override;
@ -2721,6 +2725,7 @@ begin
lSelectedController.MVCControllerBeforeDestroy;
end;
lContext.Response.ContentType := lSelectedController.ContentType;
Result := True; //handled
end; //if not handled by OnBeforeControllerActionMiddleware
ExecuteAfterControllerActionMiddleware(lContext,
lRouterControllerClazzQualifiedClassName,
@ -3710,11 +3715,9 @@ function TMVCController.GetRenderedView(const AViewNames: TArray<string>;
var
lView: TMVCBaseViewEngine; lViewName: string; lStrStream: TStringStream;
begin
Assert(not Assigned(FViewModel), 'ViewModel must not be used when JSONModel is used');
Assert(not Assigned(FViewDataSets), 'ViewDatasets must not be used when JSONModel is used');
lStrStream := TStringStream.Create('', TEncoding.UTF8);
try
lView := FEngine.ViewEngineClass.Create(Engine, Context, JSONModel, ContentType);
lView := FEngine.ViewEngineClass.Create(Engine, Context, FViewModel, FViewDataSets, JSONModel, ContentType);
try
for lViewName in AViewNames do
begin
@ -3754,6 +3757,14 @@ begin
end
else
begin
{
This useless call is required because otherwise
the IdCustomHTTPServer.pas at line 2211 (11 alexandria)
emits some useless HTML. Call Render('') cause
the internal contentstream to be created so the empty
stream is rendered and not the useless HTML.
}
Controller.Render(''); {required}
Controller.ResponseStatus(MVCResponse.StatusCode);
end;
end;
@ -4608,7 +4619,9 @@ end;
{ TMVCBaseView }
constructor TMVCBaseViewEngine.Create(const AEngine: TMVCEngine; const AWebContext: TWebContext;
constructor TMVCBaseViewEngine.Create(
const AEngine: TMVCEngine;
const AWebContext: TWebContext;
const AViewModel: TMVCViewDataObject;
const AViewDataSets: TObjectDictionary<string, TDataSet>;
const AContentType: string);
@ -4622,11 +4635,15 @@ begin
FOutput := EmptyStr;
end;
constructor TMVCBaseViewEngine.Create(const AEngine: TMVCEngine;
const AWebContext: TWebContext; const AJSONModel: TJSONObject;
constructor TMVCBaseViewEngine.Create(
const AEngine: TMVCEngine;
const AWebContext: TWebContext;
const AViewModel: TMVCViewDataObject;
const AViewDataSets: TObjectDictionary<string, TDataSet>;
const AJSONModel: TJSONObject;
const AContentType: string);
begin
Create(AEngine, AWebContext, nil, nil, AContentType);
Create(AEngine, AWebContext, AViewModel, AViewDataSets, AContentType);
fJSONModel := AJSONModel;
end;
@ -4636,7 +4653,9 @@ begin
end;
function TMVCBaseViewEngine.GetRealFileName(const AViewName: string): string;
var lFileName: string; lDefaultViewFileExtension: string;
var
lFileName: string;
lDefaultViewFileExtension: string;
begin
lDefaultViewFileExtension := Config[TMVCConfigKey.DefaultViewFileExtension];
lFileName := StringReplace(AViewName, '/', '\', [rfReplaceAll]);

View File

@ -1291,7 +1291,7 @@ begin
var lFName: string := TPath.Combine(AppPath, 'customers.json');
lDS.LoadFromFile(lFName);
ViewDataset['customers'] := lDS;
ViewData['customers2'] := lDS;
ViewDataset['customers2'] := lDS;
LoadView(['dataset_list']);
RenderResponseStream;
finally