Updated book search sample
Some checks are pending
TOC Generator / TOC Generator (push) Waiting to run

This commit is contained in:
Daniele Teti 2024-10-17 17:04:03 +02:00
parent 6817fc1ce5
commit a692a5a37e
5 changed files with 200 additions and 78 deletions

View File

@ -1,24 +1,94 @@
<!DOCTYPE html> <!DOCTYPE html>
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="UTF-8" /> <meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Books Search</title> <title>Books Search</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bulma@0.9.4/css/bulma.min.css" /> <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bulma@0.9.4/css/bulma.min.css" />
<script src="https://cdn.jsdelivr.net/npm/htmx.org/dist/htmx.min.js"></script> <script src="https://cdn.jsdelivr.net/npm/htmx.org/dist/htmx.min.js"></script>
{{block "headers"}}{{endblock}} <style>
.smooth {
transition: all 3s ease-in;
}
</style>
<style>
div {
padding: 0.2rem !important;
padding-left: 0.3rem !important;
}
div.blueTable {
font-family: Tahoma, Geneva, sans-serif;
border: 1px solid #1C6EA4;
background-color: #EEEEEE;
width: 100%;
text-align: left;
border-collapse: collapse;
}
.divTable.blueTable .divTableCell, .divTable.blueTable .divTableHead {
padding: 3px 2px;
}
.divTable.blueTable .divTableBody .divTableCell {
font-size: 14px;
}
.divTable.blueTable .divTableRow:nth-child(even) {
background: #D0E4F5;
}
.divTable.blueTable .divTableHeading {
background: #1C6EA4;
border-bottom: 2px solid #444444;
}
.divTable.blueTable .divTableHeading .divTableHead {
font-size: 18px;
color: #FFFFFF;
text-align: left;
}
.blueTable .tableFootStyle {
font-size: 14px;
}
.blueTable .tableFootStyle .links {
text-align: right;
}
.blueTable .tableFootStyle .links a{
display: inline-block;
background: #1C6EA4;
color: #FFFFFF;
padding: 2px 8px;
border-radius: 5px;
}
.blueTable.outerTableFooter {
border-top: none;
}
.blueTable.outerTableFooter .tableFootStyle {
padding: 3px 5px;
}
/* HTMLtable.com */
.divTable{ display: table; }
.divTableRow { display: table-row; }
.divTableHeading { display: table-header-group;}
.divTableCell, .divTableHead { display: table-cell; }
.divTableHeading { display: table-header-group;}
.divTableFoot { display: table-footer-group;}
.divTableBody { display: table-row-group;}
</style>
{{block "headers"}}{{endblock}}
</head> </head>
<body hx-indicator="#spinner"> <body hx-indicator="#spinner">
<section class="section" > <section class="section">
{{block "body"}}{{endblock}} {{block "body"}}{{endblock}}
</section> </section>
<div class="content has-text-centered"> <div class="content has-text-centered">
<p> <p>
<strong><a href="https://github.com/danieleteti/templatepro">TemplatePRO</a> {{""|version}}</strong> by <a href="https://www.danieleteti.it">Daniele Teti</a>. <strong><a href="https://github.com/danieleteti/templatepro">TemplatePRO</a> {{:|version}}</strong> by <a
href="https://www.danieleteti.it">Daniele Teti</a>.
The source code is licensed <a href="https://github.com/danieleteti/templatepro?tab=Apache-2.0-1-ov-file#readme">Apache 2.0</a>.
</p> The source code is licensed <a
</div> href="https://github.com/danieleteti/templatepro?tab=Apache-2.0-1-ov-file#readme">Apache 2.0</a>.
</p>
</div>
</body> </body>
</html> </html>

View File

@ -1,15 +1,19 @@
{{extends "baselayout.html"}} {{extends "baselayout.html"}}
{{block "body"}} {{block "body"}}
<div class="columns"> <div class="columns">
<div class="column is-one-third is-offset-one-third"> <div class="column is-offset-one-third is-one-third">
<input type="text" class="input" placeholder="Search" name="q" hx-get="/search" <h1 style="text-align: center; font-weight: 500; font-size: 170%">Instant Search Demo</h1>
value="{{""|fromquery,"q"}}" <h2 style="text-align: center; font-size: 130%; color: #a0a0a0">DMVCFramework + TemplatePro + HTMX</h2>
hx-trigger="load, keyup changed delay:500ms" hx-target="#results"/> </div>
</div> </div>
<div class="column is-one-third"> <div class="columns">
<p class="subtitle is-6">⭐ DMVCFramework + HTMX :: Instant Search Demo ⭐</p> <div class="column is-one-third is-offset-one-third">
<input type="text" class="input" placeholder="Search" name="q" hx-get="/search"
value="{{:|fromquery,"q"}}"
hx-trigger="load, keyup changed delay:500ms"
hx-target="#results"
hx-swap="transition:true"/>
</div> </div>
</div> </div>
<progress id="spinner" class="htmx-indicator progress is-large is-info" max="100">ciao</progress>
<div id="results"></div> <div id="results"></div>
{{endblock}} {{endblock}}

View File

@ -1,47 +1,37 @@
<p>{{:books|count}} book/s found</p> <p>{{:books|count}} book/s found</p>
<table class="table is-fullwidth is-hoverable">
<thead> <div class="divTable blueTable">
<tr> <div class="divTableHeading">
<th>ID</th> <div class="divTableRow">
<th>Book Title</th> <div class="divTableHead">ID</div>
<th>Book Author</th> <div class="divTableHead">Book Title</div>
<th>Rating</th> <div class="divTableHead">Book Author</div>
<th>Genre</th> <div class="divTableHead">Rating</div>
</tr> <div class="divTableHead">Genre</div>
</thead> </div>
<tbody> </div>
{{if !books}} <div class="divTableBody">
<tr> {{if !books}}
<td colspan="4"> <div class="divTableRow">
<span>No books found</span> <span>No books found</span>
</td> </div>
</tr> {{endif}}
{{endif}} {{for book in books}}
{{for book in books}} <div class="divTableRow {{if book.genre|eq,"Horror"}}has-background-danger-light{{endif}}">
<tr class= <div class="divTableCell">{{:book.id|lpad,8,"0"}}</div>
{{if book.genre|eq,"Horror"}} <div class="divTableCell">{{:book.book_name}}</div>
"has-background-danger-light" <div class="divTableCell">{{:book.author_name}}</div>
{{else}} {{if book.genre|eq,"Thriller"}} <div class="divTableCell">
"has-background-success-light" {{if book.rating|ge,1}}⭐{{endif}}
{{else}} {{if book.genre|eq,"Classic"}} {{if book.rating|ge,2}}⭐{{endif}}
"has-background-primary-light" {{if book.rating|ge,3}}⭐{{endif}}
{{endif}} {{if book.rating|ge,4}}⭐{{endif}}
{{endif}} {{if book.rating|ge,5}}⭐{{endif}}
{{endif}}> </div>
<td>{{:book.id|lpad,8,"0"}}</td> <div class="divTableCell">
<td>{{:book.book_name}}</td> {{:book.genre|uppercase}}
<td>{{:book.author_name}}</td> </div>
<td> </div>
{{if book.rating|ge,1}}⭐{{endif}} {{endfor}}
{{if book.rating|ge,2}}⭐{{endif}} </div>
{{if book.rating|ge,3}}⭐{{endif}} </div>
{{if book.rating|ge,4}}⭐{{endif}}
{{if book.rating|ge,5}}⭐{{endif}}
</td>
<td>
{{:book.genre|uppercase}}
</td>
</tr>
{{endfor}}
</tbody>
</table>

View File

@ -179,13 +179,17 @@ begin
lCompiledTemplate.AddFilter('fromquery', lCompiledTemplate.AddFilter('fromquery',
function (const aValue: TValue; const aParameters: TArray<string>): TValue function (const aValue: TValue; const aParameters: TArray<string>): TValue
begin begin
if not aValue.IsEmpty then
begin
raise ETProRenderException.Create('Filter "fromquery" cannot be applied to a value [HINT] Use {{:|fromquery,"parname"}}');
end;
if Length(aParameters) = 1 then if Length(aParameters) = 1 then
begin begin
Result := Self.WebContext.Request.QueryStringParam(aParameters[0]); Result := Self.WebContext.Request.QueryStringParam(aParameters[0]);
end end
else else
begin begin
Result := '(Error: Expected 1 param, got ' + Length(aParameters).ToString + ')'; raise ETProRenderException.Create('Expected 1 param for filter "fromquery", got ' + Length(aParameters).ToString);
end; end;
end); end);
if Assigned(FBeforeRenderCallback) then if Assigned(FBeforeRenderCallback) then

View File

@ -35,7 +35,7 @@ uses
System.RTTI; System.RTTI;
const const
TEMPLATEPRO_VERSION = '0.7.0'; TEMPLATEPRO_VERSION = '0.7.1';
type type
ETProException = class(Exception) ETProException = class(Exception)
@ -241,6 +241,7 @@ type
procedure ProcessJumps(const aTokens: TList<TToken>); procedure ProcessJumps(const aTokens: TList<TToken>);
procedure Compile(const aTemplate: string; const aTokens: TList<TToken>; const aFileNameRefPath: String); overload; procedure Compile(const aTemplate: string; const aTokens: TList<TToken>; const aFileNameRefPath: String); overload;
constructor Create(const aEncoding: TEncoding; const aOptions: TTProCompilerOptions = []); overload; constructor Create(const aEncoding: TEncoding; const aOptions: TTProCompilerOptions = []); overload;
procedure MatchFilter(lVarName: string; var lFuncName: string; var lFuncParamsCount: Integer; var lFuncParams: TArray<String>);
public public
function Compile(const aTemplate: string; const aFileNameRefPath: String = ''): ITProCompiledTemplate; overload; function Compile(const aTemplate: string; const aFileNameRefPath: String = ''): ITProCompiledTemplate; overload;
constructor Create(aEncoding: TEncoding = nil); overload; constructor Create(aEncoding: TEncoding = nil); overload;
@ -333,7 +334,7 @@ end;
procedure FunctionError(const aFunctionName, aErrMessage: string); procedure FunctionError(const aFunctionName, aErrMessage: string);
begin begin
raise ETProRenderException.Create(Format('%s in function %s', [aErrMessage, aFunctionName])) at ReturnAddress; raise ETProRenderException.Create(Format('[%1:s] %0:s (error in filter call for function [%1:s])', [aErrMessage, aFunctionName])) at ReturnAddress;
end; end;
function _Comparand(const aComparandType: TComparandType; const aValue: TValue; const aParameters: TArray<String>; const aLocaleFormatSettings: TFormatSettings): TValue; function _Comparand(const aComparandType: TComparandType; const aValue: TValue; const aParameters: TArray<String>; const aLocaleFormatSettings: TFormatSettings): TValue;
@ -701,6 +702,17 @@ begin
Create(aEncoding, []); Create(aEncoding, []);
end; end;
procedure TTProCompiler.MatchFilter(lVarName: string; var lFuncName: string; var lFuncParamsCount: Integer; var lFuncParams: TArray<String>);
begin
MatchSpace;
if not MatchVariable(lFuncName) then
Error('Invalid function name applied to variable ' + lVarName);
MatchSpace;
lFuncParams := GetFunctionParameters;
lFuncParamsCount := Length(lFuncParams);
MatchSpace;
end;
function TTProCompiler.CurrentChar: Char; function TTProCompiler.CurrentChar: Char;
begin begin
Result := fInputString.Chars[fCharIndex] Result := fInputString.Chars[fCharIndex]
@ -899,6 +911,8 @@ var
lContentOnThisLine: Integer; lContentOnThisLine: Integer;
lStrVerbatim: string; lStrVerbatim: string;
lLayoutFound: Boolean; lLayoutFound: Boolean;
lFoundVar: Boolean;
lFoundFilter: Boolean;
begin begin
aTokens.Add(TToken.Create(ttSystemVersion, TEMPLATEPRO_VERSION, '')); aTokens.Add(TToken.Create(ttSystemVersion, TEMPLATEPRO_VERSION, ''));
lLastToken := ttEOF; lLastToken := ttEOF;
@ -978,9 +992,12 @@ begin
if CurrentChar = ':' then // variable if CurrentChar = ':' then // variable
begin begin
lFoundVar := False;
lFoundFilter := False;
Step; Step;
if MatchVariable(lVarName) then { variable } if MatchVariable(lVarName) then { variable }
begin begin
lFoundVar := True;
if lVarName.IsEmpty then if lVarName.IsEmpty then
Error('Invalid variable name'); Error('Invalid variable name');
lFuncName := ''; lFuncName := '';
@ -990,15 +1007,17 @@ begin
MatchSpace; MatchSpace;
if MatchSymbol('|') then if MatchSymbol('|') then
begin begin
MatchSpace; MatchFilter(lVarName, lFuncName, lFuncParamsCount, lFuncParams);
if not MatchVariable(lFuncName) then
Error('Invalid function name applied to variable ' + lVarName);
MatchSpace;
lFuncParams := GetFunctionParameters;
lFuncParamsCount := Length(lFuncParams);
MatchSpace;
end; end;
end
else if MatchSymbol('|') then
begin
lFoundFilter := True;
MatchFilter(lVarName, lFuncName, lFuncParamsCount, lFuncParams);
end;
if lFoundVar or lFoundFilter then
begin
if not MatchEndTag then if not MatchEndTag then
begin begin
Error('Expected end tag "' + END_TAG + '" near ' + GetSubsequentText); Error('Expected end tag "' + END_TAG + '" near ' + GetSubsequentText);
@ -1020,7 +1039,11 @@ begin
end; end;
end; end;
end; end;
end; // matchvariable end
else
begin
Error('Expected variable or filter near ' + GetSubsequentText);
end;
end end
else else
begin begin
@ -1631,15 +1654,42 @@ begin
end end
else if SameText(aFunctionName, 'uppercase') then else if SameText(aFunctionName, 'uppercase') then
begin begin
Result := UpperCase(aValue.AsString); if not aValue.IsEmpty then
begin
CheckParNumber(0, aParameters);
Result := UpperCase(aValue.AsString);
end
else
begin
CheckParNumber(1, aParameters);
Result := UpperCase(aParameters[0]);
end;
end end
else if SameText(aFunctionName, 'lowercase') then else if SameText(aFunctionName, 'lowercase') then
begin begin
Result := lowercase(aValue.AsString); if not aValue.IsEmpty then
begin
CheckParNumber(0, aParameters);
Result := lowercase(aValue.AsString);
end
else
begin
CheckParNumber(1, aParameters);
Result := lowercase(aParameters[0]);
end;
end end
else if SameText(aFunctionName, 'capitalize') then else if SameText(aFunctionName, 'capitalize') then
begin begin
Result := CapitalizeString(aValue.AsString, True); if not aValue.IsEmpty then
begin
CheckParNumber(0, aParameters);
Result := CapitalizeString(aValue.AsString, True);
end
else
begin
CheckParNumber(1, aParameters);
Result := CapitalizeString(aParameters[0], True);
end;
end end
else if SameText(aFunctionName, 'trunc') then else if SameText(aFunctionName, 'trunc') then
begin begin
@ -1760,6 +1810,10 @@ begin
end end
else if SameText(aFunctionName, 'version') then else if SameText(aFunctionName, 'version') then
begin begin
if not aValue.IsEmpty then
begin
FunctionError(aFunctionName, 'cannot be applied to a value - [HINT] Use {{:|' + aFunctionName + '}}');
end;
CheckParNumber(0, aParameters); CheckParNumber(0, aParameters);
Result := TEMPLATEPRO_VERSION; Result := TEMPLATEPRO_VERSION;
end end