2017-10-09 10:41:38 +02:00
|
|
|
// ***************************************************************************
|
|
|
|
//
|
|
|
|
// Delphi MVC Framework
|
|
|
|
//
|
2024-01-02 17:04:27 +01:00
|
|
|
// Copyright (c) 2010-2024 Daniele Teti and the DMVCFramework Team
|
2017-10-09 10:41:38 +02:00
|
|
|
//
|
|
|
|
// 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 MVCFramework.View.Renderers.TemplatePro;
|
|
|
|
|
|
|
|
interface
|
|
|
|
|
|
|
|
uses
|
|
|
|
MVCFramework, System.Generics.Collections, System.SysUtils,
|
2018-01-29 17:30:53 +01:00
|
|
|
MVCFramework.Commons, System.IOUtils, System.Classes;
|
2017-10-09 10:41:38 +02:00
|
|
|
|
|
|
|
type
|
2017-10-09 16:17:12 +02:00
|
|
|
{ This class implements the TemplatePro view engine for server side views }
|
2017-10-09 10:41:38 +02:00
|
|
|
TMVCTemplateProViewEngine = class(TMVCBaseViewEngine)
|
|
|
|
public
|
2024-08-18 11:40:14 +02:00
|
|
|
procedure Execute(const ViewName: string; const Builder: TStringBuilder); override;
|
2017-10-09 10:41:38 +02:00
|
|
|
end;
|
|
|
|
|
|
|
|
implementation
|
|
|
|
|
|
|
|
uses
|
|
|
|
MVCFramework.Serializer.Defaults,
|
|
|
|
MVCFramework.Serializer.Intf,
|
|
|
|
MVCFramework.DuckTyping,
|
2024-08-12 10:38:16 +02:00
|
|
|
TemplatePro,
|
2017-10-09 10:41:38 +02:00
|
|
|
MVCFramework.Cache,
|
2024-08-18 11:19:59 +02:00
|
|
|
Data.DB,
|
|
|
|
System.Rtti,
|
|
|
|
JsonDataObjects;
|
2017-10-09 10:41:38 +02:00
|
|
|
|
|
|
|
{$WARNINGS OFF}
|
|
|
|
|
2024-08-24 16:32:38 +02:00
|
|
|
function GetDataSetOrObjectListCount(const aValue: TValue; const aParameters: TArray<string>): TValue;
|
2024-09-02 20:04:44 +02:00
|
|
|
var
|
|
|
|
lWrappedList: IMVCList;
|
2024-08-19 18:08:34 +02:00
|
|
|
begin
|
2024-09-02 20:04:44 +02:00
|
|
|
if not aValue.IsObject then
|
|
|
|
begin
|
|
|
|
Result := False;
|
|
|
|
end;
|
|
|
|
|
2024-09-22 17:05:07 +02:00
|
|
|
if Length(aParameters) <> 0 then
|
|
|
|
begin
|
|
|
|
Result := '(Error: Expected 0 params, got ' + Length(aParameters).ToString + ')';
|
|
|
|
end;
|
|
|
|
|
2024-09-02 20:04:44 +02:00
|
|
|
if aValue.AsObject is TDataSet then
|
|
|
|
begin
|
|
|
|
Result := TDataSet(aValue.AsObject).RecordCount;
|
|
|
|
end
|
|
|
|
else if aValue.AsObject is TJsonArray then
|
|
|
|
begin
|
|
|
|
Result := TJsonArray(aValue.AsObject).Count;
|
|
|
|
end
|
|
|
|
else if aValue.AsObject is TJsonObject then
|
|
|
|
begin
|
|
|
|
Result := TJsonObject(aValue.AsObject).Count;
|
|
|
|
end
|
|
|
|
else
|
|
|
|
begin
|
2024-10-07 16:53:02 +02:00
|
|
|
if (aValue.AsObject <> nil) and TDuckTypedList.CanBeWrappedAsList(aValue.AsObject, lWrappedList) then
|
2024-09-02 20:04:44 +02:00
|
|
|
begin
|
|
|
|
Result := lWrappedList.Count;
|
|
|
|
end
|
|
|
|
else
|
|
|
|
begin
|
|
|
|
Result := False;
|
|
|
|
end;
|
|
|
|
end;
|
2024-08-19 18:08:34 +02:00
|
|
|
end;
|
|
|
|
|
2024-08-24 16:32:38 +02:00
|
|
|
function DumpAsJSONString(const aValue: TValue; const aParameters: TArray<string>): TValue;
|
2024-08-18 11:19:59 +02:00
|
|
|
var
|
|
|
|
lWrappedList: IMVCList;
|
|
|
|
begin
|
|
|
|
if not aValue.IsObject then
|
|
|
|
begin
|
2024-11-03 22:48:04 +01:00
|
|
|
if aValue.IsType<Int64> then
|
|
|
|
begin
|
|
|
|
Exit(aValue.AsInt64);
|
|
|
|
end;
|
|
|
|
Exit('(Error: Cannot serialize non-object as JSON)');
|
2024-08-18 11:19:59 +02:00
|
|
|
end;
|
|
|
|
|
|
|
|
if TDuckTypedList.CanBeWrappedAsList(aValue.AsObject, lWrappedList) then
|
|
|
|
begin
|
|
|
|
Result := GetDefaultSerializer.SerializeCollection(lWrappedList)
|
|
|
|
end
|
|
|
|
else
|
|
|
|
begin
|
|
|
|
if aValue.AsObject is TDataSet then
|
|
|
|
Result := GetDefaultSerializer.SerializeDataSet(TDataSet(aValue.AsObject))
|
|
|
|
else
|
|
|
|
Result := GetDefaultSerializer.SerializeObject(aValue.AsObject);
|
|
|
|
end;
|
|
|
|
end;
|
|
|
|
|
2024-08-18 11:40:14 +02:00
|
|
|
procedure TMVCTemplateProViewEngine.Execute(const ViewName: string; const Builder: TStringBuilder);
|
2017-10-09 10:41:38 +02:00
|
|
|
var
|
2024-08-12 10:38:16 +02:00
|
|
|
lTP: TTProCompiler;
|
2017-10-09 10:41:38 +02:00
|
|
|
lViewFileName: string;
|
2024-09-02 20:04:44 +02:00
|
|
|
lViewTemplate: String;
|
2024-08-12 10:38:16 +02:00
|
|
|
lCompiledTemplate: ITProCompiledTemplate;
|
|
|
|
lPair: TPair<String, TValue>;
|
2024-08-19 18:08:34 +02:00
|
|
|
lActualFileTimeStamp: TDateTime;
|
|
|
|
lCompiledViewFileName: string;
|
|
|
|
lActualCompiledFileTimeStamp: TDateTime;
|
|
|
|
lUseCompiledVersion: Boolean;
|
|
|
|
lCacheDir: string;
|
2017-10-09 10:41:38 +02:00
|
|
|
begin
|
2024-08-19 18:08:34 +02:00
|
|
|
lUseCompiledVersion := False;
|
|
|
|
lViewFileName := GetRealFileName(ViewName);
|
2024-09-11 11:15:38 +02:00
|
|
|
if lViewFileName.IsEmpty then
|
2024-10-14 09:21:56 +02:00
|
|
|
raise EMVCSSVException.CreateFmt('View [%s] not found', [ViewName]);
|
2024-09-11 11:15:38 +02:00
|
|
|
if FUseViewCache then
|
2024-08-19 18:08:34 +02:00
|
|
|
begin
|
2024-08-24 16:32:38 +02:00
|
|
|
lCacheDir := TPath.Combine(TPath.GetDirectoryName(lViewFileName), '__cache__');
|
|
|
|
TDirectory.CreateDirectory(lCacheDir);
|
|
|
|
lCompiledViewFileName := TPath.Combine(lCacheDir, TPath.ChangeExtension(TPath.GetFileName(lViewFileName), '.' + TEMPLATEPRO_VERSION + '.tpcu'));
|
2024-08-19 18:08:34 +02:00
|
|
|
|
2024-08-24 16:32:38 +02:00
|
|
|
if not FileAge(lViewFileName, lActualFileTimeStamp) then
|
|
|
|
begin
|
2024-10-14 09:21:56 +02:00
|
|
|
raise EMVCSSVException.CreateFmt('View [%s] not found',
|
2024-08-24 16:32:38 +02:00
|
|
|
[ViewName]);
|
|
|
|
end;
|
|
|
|
|
|
|
|
if FileAge(lCompiledViewFileName, lActualCompiledFileTimeStamp) then
|
|
|
|
begin
|
|
|
|
lUseCompiledVersion := lActualFileTimeStamp < lActualCompiledFileTimeStamp;
|
|
|
|
end;
|
2024-08-19 18:08:34 +02:00
|
|
|
end;
|
2017-10-09 10:41:38 +02:00
|
|
|
|
2024-08-19 18:08:34 +02:00
|
|
|
if lUseCompiledVersion then
|
|
|
|
begin
|
|
|
|
lCompiledTemplate := TTProCompiledTemplate.CreateFromFile(lCompiledViewFileName);
|
|
|
|
end
|
|
|
|
else
|
|
|
|
begin
|
|
|
|
lTP := TTProCompiler.Create;
|
2017-10-09 10:41:38 +02:00
|
|
|
try
|
2024-08-19 18:08:34 +02:00
|
|
|
lViewTemplate := TFile.ReadAllText(lViewFileName);
|
2024-08-12 10:38:16 +02:00
|
|
|
lCompiledTemplate := lTP.Compile(lViewTemplate, lViewFileName);
|
2024-09-11 11:15:38 +02:00
|
|
|
if FUseViewCache then
|
2024-08-24 16:32:38 +02:00
|
|
|
begin
|
|
|
|
lCompiledTemplate.SaveToFile(lCompiledViewFileName);
|
|
|
|
end;
|
2024-08-19 18:08:34 +02:00
|
|
|
finally
|
|
|
|
lTP.Free;
|
|
|
|
end;
|
|
|
|
end;
|
|
|
|
|
|
|
|
try
|
|
|
|
if Assigned(ViewModel) then
|
|
|
|
begin
|
|
|
|
for lPair in ViewModel do
|
2018-01-29 17:30:53 +01:00
|
|
|
begin
|
2024-08-19 18:08:34 +02:00
|
|
|
lCompiledTemplate.SetData(lPair.Key, ViewModel[lPair.Key]);
|
2018-01-29 17:30:53 +01:00
|
|
|
end;
|
2017-10-09 10:41:38 +02:00
|
|
|
end;
|
2024-08-19 18:08:34 +02:00
|
|
|
lCompiledTemplate.AddFilter('json', DumpAsJSONString);
|
|
|
|
lCompiledTemplate.AddFilter('count', GetDataSetOrObjectListCount);
|
2024-10-07 16:53:02 +02:00
|
|
|
lCompiledTemplate.AddFilter('fromquery',
|
2024-09-22 17:05:07 +02:00
|
|
|
function (const aValue: TValue; const aParameters: TArray<string>): TValue
|
|
|
|
begin
|
2024-10-17 17:04:03 +02:00
|
|
|
if not aValue.IsEmpty then
|
|
|
|
begin
|
|
|
|
raise ETProRenderException.Create('Filter "fromquery" cannot be applied to a value [HINT] Use {{:|fromquery,"parname"}}');
|
|
|
|
end;
|
2024-09-22 17:05:07 +02:00
|
|
|
if Length(aParameters) = 1 then
|
|
|
|
begin
|
|
|
|
Result := Self.WebContext.Request.QueryStringParam(aParameters[0]);
|
|
|
|
end
|
|
|
|
else
|
|
|
|
begin
|
2024-10-17 17:04:03 +02:00
|
|
|
raise ETProRenderException.Create('Expected 1 param for filter "fromquery", got ' + Length(aParameters).ToString);
|
2024-09-22 17:05:07 +02:00
|
|
|
end;
|
|
|
|
end);
|
2024-10-14 09:21:56 +02:00
|
|
|
if Assigned(FBeforeRenderCallback) then
|
|
|
|
begin
|
|
|
|
FBeforeRenderCallback(TObject(lCompiledTemplate));
|
|
|
|
end;
|
2024-08-19 18:08:34 +02:00
|
|
|
Builder.Append(lCompiledTemplate.Render);
|
|
|
|
except
|
|
|
|
on E: ETProException do
|
|
|
|
begin
|
|
|
|
raise EMVCViewError.CreateFmt('View [%s] error: %s (%s)',
|
|
|
|
[ViewName, E.Message, E.ClassName]);
|
|
|
|
end;
|
2017-10-09 10:41:38 +02:00
|
|
|
end;
|
|
|
|
end;
|
|
|
|
|
|
|
|
end.
|