mirror of
https://github.com/danieleteti/delphimvcframework.git
synced 2024-11-15 07:45:54 +01:00
Updated book search sample
Some checks are pending
TOC Generator / TOC Generator (push) Waiting to run
Some checks are pending
TOC Generator / TOC Generator (push) Waiting to run
This commit is contained in:
parent
6817fc1ce5
commit
a692a5a37e
@ -1,24 +1,94 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Books Search</title>
|
||||
<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>
|
||||
{{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>
|
||||
|
||||
<body hx-indicator="#spinner">
|
||||
<section class="section" >
|
||||
<section class="section">
|
||||
{{block "body"}}{{endblock}}
|
||||
</section>
|
||||
|
||||
<div class="content has-text-centered">
|
||||
<p>
|
||||
<strong><a href="https://github.com/danieleteti/templatepro">TemplatePRO</a> {{""|version}}</strong> by <a href="https://www.danieleteti.it">Daniele Teti</a>.
|
||||
<div class="content has-text-centered">
|
||||
<p>
|
||||
<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>
|
||||
</div>
|
||||
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>
|
||||
</div>
|
||||
</body>
|
||||
|
||||
</html>
|
@ -1,15 +1,19 @@
|
||||
{{extends "baselayout.html"}}
|
||||
{{block "body"}}
|
||||
<div class="columns">
|
||||
<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"/>
|
||||
</div>
|
||||
<div class="column is-one-third">
|
||||
<p class="subtitle is-6">⭐ DMVCFramework + HTMX :: Instant Search Demo ⭐</p>
|
||||
<div class="column is-offset-one-third is-one-third">
|
||||
<h1 style="text-align: center; font-weight: 500; font-size: 170%">Instant Search Demo</h1>
|
||||
<h2 style="text-align: center; font-size: 130%; color: #a0a0a0">DMVCFramework + TemplatePro + HTMX</h2>
|
||||
</div>
|
||||
</div>
|
||||
<div class="columns">
|
||||
<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>
|
||||
<progress id="spinner" class="htmx-indicator progress is-large is-info" max="100">ciao</progress>
|
||||
<div id="results"></div>
|
||||
{{endblock}}
|
@ -1,47 +1,37 @@
|
||||
<p>{{:books|count}} book/s found</p>
|
||||
<table class="table is-fullwidth is-hoverable">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>ID</th>
|
||||
<th>Book Title</th>
|
||||
<th>Book Author</th>
|
||||
<th>Rating</th>
|
||||
<th>Genre</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{{if !books}}
|
||||
<tr>
|
||||
<td colspan="4">
|
||||
<span>No books found</span>
|
||||
</td>
|
||||
</tr>
|
||||
{{endif}}
|
||||
{{for book in books}}
|
||||
<tr class=
|
||||
{{if book.genre|eq,"Horror"}}
|
||||
"has-background-danger-light"
|
||||
{{else}} {{if book.genre|eq,"Thriller"}}
|
||||
"has-background-success-light"
|
||||
{{else}} {{if book.genre|eq,"Classic"}}
|
||||
"has-background-primary-light"
|
||||
{{endif}}
|
||||
|
||||
<div class="divTable blueTable">
|
||||
<div class="divTableHeading">
|
||||
<div class="divTableRow">
|
||||
<div class="divTableHead">ID</div>
|
||||
<div class="divTableHead">Book Title</div>
|
||||
<div class="divTableHead">Book Author</div>
|
||||
<div class="divTableHead">Rating</div>
|
||||
<div class="divTableHead">Genre</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="divTableBody">
|
||||
{{if !books}}
|
||||
<div class="divTableRow">
|
||||
<span>No books found</span>
|
||||
</div>
|
||||
{{endif}}
|
||||
{{endif}}>
|
||||
<td>{{:book.id|lpad,8,"0"}}</td>
|
||||
<td>{{:book.book_name}}</td>
|
||||
<td>{{:book.author_name}}</td>
|
||||
<td>
|
||||
{{if book.rating|ge,1}}⭐{{endif}}
|
||||
{{if book.rating|ge,2}}⭐{{endif}}
|
||||
{{if book.rating|ge,3}}⭐{{endif}}
|
||||
{{if book.rating|ge,4}}⭐{{endif}}
|
||||
{{if book.rating|ge,5}}⭐{{endif}}
|
||||
</td>
|
||||
<td>
|
||||
{{:book.genre|uppercase}}
|
||||
</td>
|
||||
</tr>
|
||||
{{endfor}}
|
||||
</tbody>
|
||||
</table>
|
||||
{{for book in books}}
|
||||
<div class="divTableRow {{if book.genre|eq,"Horror"}}has-background-danger-light{{endif}}">
|
||||
<div class="divTableCell">{{:book.id|lpad,8,"0"}}</div>
|
||||
<div class="divTableCell">{{:book.book_name}}</div>
|
||||
<div class="divTableCell">{{:book.author_name}}</div>
|
||||
<div class="divTableCell">
|
||||
{{if book.rating|ge,1}}⭐{{endif}}
|
||||
{{if book.rating|ge,2}}⭐{{endif}}
|
||||
{{if book.rating|ge,3}}⭐{{endif}}
|
||||
{{if book.rating|ge,4}}⭐{{endif}}
|
||||
{{if book.rating|ge,5}}⭐{{endif}}
|
||||
</div>
|
||||
<div class="divTableCell">
|
||||
{{:book.genre|uppercase}}
|
||||
</div>
|
||||
</div>
|
||||
{{endfor}}
|
||||
</div>
|
||||
</div>
|
||||
|
@ -179,13 +179,17 @@ begin
|
||||
lCompiledTemplate.AddFilter('fromquery',
|
||||
function (const aValue: TValue; const aParameters: TArray<string>): TValue
|
||||
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
|
||||
begin
|
||||
Result := Self.WebContext.Request.QueryStringParam(aParameters[0]);
|
||||
end
|
||||
else
|
||||
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);
|
||||
if Assigned(FBeforeRenderCallback) then
|
||||
|
@ -35,7 +35,7 @@ uses
|
||||
System.RTTI;
|
||||
|
||||
const
|
||||
TEMPLATEPRO_VERSION = '0.7.0';
|
||||
TEMPLATEPRO_VERSION = '0.7.1';
|
||||
|
||||
type
|
||||
ETProException = class(Exception)
|
||||
@ -241,6 +241,7 @@ type
|
||||
procedure ProcessJumps(const aTokens: TList<TToken>);
|
||||
procedure Compile(const aTemplate: string; const aTokens: TList<TToken>; const aFileNameRefPath: String); 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
|
||||
function Compile(const aTemplate: string; const aFileNameRefPath: String = ''): ITProCompiledTemplate; overload;
|
||||
constructor Create(aEncoding: TEncoding = nil); overload;
|
||||
@ -333,7 +334,7 @@ end;
|
||||
|
||||
procedure FunctionError(const aFunctionName, aErrMessage: string);
|
||||
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;
|
||||
|
||||
function _Comparand(const aComparandType: TComparandType; const aValue: TValue; const aParameters: TArray<String>; const aLocaleFormatSettings: TFormatSettings): TValue;
|
||||
@ -701,6 +702,17 @@ begin
|
||||
Create(aEncoding, []);
|
||||
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;
|
||||
begin
|
||||
Result := fInputString.Chars[fCharIndex]
|
||||
@ -899,6 +911,8 @@ var
|
||||
lContentOnThisLine: Integer;
|
||||
lStrVerbatim: string;
|
||||
lLayoutFound: Boolean;
|
||||
lFoundVar: Boolean;
|
||||
lFoundFilter: Boolean;
|
||||
begin
|
||||
aTokens.Add(TToken.Create(ttSystemVersion, TEMPLATEPRO_VERSION, ''));
|
||||
lLastToken := ttEOF;
|
||||
@ -978,9 +992,12 @@ begin
|
||||
|
||||
if CurrentChar = ':' then // variable
|
||||
begin
|
||||
lFoundVar := False;
|
||||
lFoundFilter := False;
|
||||
Step;
|
||||
if MatchVariable(lVarName) then { variable }
|
||||
begin
|
||||
lFoundVar := True;
|
||||
if lVarName.IsEmpty then
|
||||
Error('Invalid variable name');
|
||||
lFuncName := '';
|
||||
@ -990,15 +1007,17 @@ begin
|
||||
MatchSpace;
|
||||
if MatchSymbol('|') then
|
||||
begin
|
||||
MatchSpace;
|
||||
if not MatchVariable(lFuncName) then
|
||||
Error('Invalid function name applied to variable ' + lVarName);
|
||||
MatchSpace;
|
||||
lFuncParams := GetFunctionParameters;
|
||||
lFuncParamsCount := Length(lFuncParams);
|
||||
MatchSpace;
|
||||
MatchFilter(lVarName, lFuncName, lFuncParamsCount, lFuncParams);
|
||||
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
|
||||
begin
|
||||
Error('Expected end tag "' + END_TAG + '" near ' + GetSubsequentText);
|
||||
@ -1020,7 +1039,11 @@ begin
|
||||
end;
|
||||
end;
|
||||
end;
|
||||
end; // matchvariable
|
||||
end
|
||||
else
|
||||
begin
|
||||
Error('Expected variable or filter near ' + GetSubsequentText);
|
||||
end;
|
||||
end
|
||||
else
|
||||
begin
|
||||
@ -1631,15 +1654,42 @@ begin
|
||||
end
|
||||
else if SameText(aFunctionName, 'uppercase') then
|
||||
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
|
||||
else if SameText(aFunctionName, 'lowercase') then
|
||||
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
|
||||
else if SameText(aFunctionName, 'capitalize') then
|
||||
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
|
||||
else if SameText(aFunctionName, 'trunc') then
|
||||
begin
|
||||
@ -1760,6 +1810,10 @@ begin
|
||||
end
|
||||
else if SameText(aFunctionName, 'version') then
|
||||
begin
|
||||
if not aValue.IsEmpty then
|
||||
begin
|
||||
FunctionError(aFunctionName, 'cannot be applied to a value - [HINT] Use {{:|' + aFunctionName + '}}');
|
||||
end;
|
||||
CheckParNumber(0, aParameters);
|
||||
Result := TEMPLATEPRO_VERSION;
|
||||
end
|
||||
|
Loading…
Reference in New Issue
Block a user