2025/03/28

回歸網頁設計的原點 - HTMX

作者:吳祐賓













HTMX 和 VUE 一樣,是由個人開發的一款開源網頁函式庫 (Library,但 VUE 屬於 Framework,特此說明),前身名為 intercooler.js,由 Carson Gross 於 2013 年打造出的產品,在 2020 年改為 HTMX 後開始有知名度


▋HTMX 框架的核心目標



HTMX 核心目標,是為了讓 AJAX 變得簡單,丟掉 <a>、<form>


使用 html 標籤就能完成 AJAX 瑣碎的 JS 程式碼工作


2020 年在 HTMX 成立之初,就有多數開發者認為比 React、VUE、Angular 還要成熟的框架


由於 HTMX 和 React 同為 Library,所以,使用 React + HTMX 關鍵字查詢時,你會優先得到 HTMX is React killer 的比較資訊


更多的是教你如何從 React 移植到 HTMX 上


▋HTMX 實際設計



實際體驗相當簡單,不需要 Web Server,在單機就能體驗


▍首先,建立一個 html 檔案



▍在 html 引入 HTMX



雖然不用寫 js,但還是需要引入 HTMX 套件,另外,範例中會呼叫外部 API,所以要設定 "selRequestsOnly" 為 "false"


<script src="https://unpkg.com/htmx.org@2.0.4" integrity="sha384-HGfztofotfshcF7+8n44JQL2oJmowVChPTg48S+jvZoztPfvwD79OC/LTtG6dMp+" crossorigin="anonymous"></script>

<meta name="htmx-config" content='{"selfRequestsOnly": false}' />


▍建立 div,設定 HTMX 呼叫 API



沒有 API 怎麼辦?


你可以參考我的另一篇 "My JSON Server -- 偽線上 REST 服務",把 API 連結套入進來


在 body 標籤裡寫下


<div hx-get="https://my-json-server.typicode.com/Eden5Wu/react-store-api/products">Download Products</div>



你應該就可以看到如圖的成果展示

 




▋總結:HTMX 將 ajax 完美的整合到 html 裡



從上面的範例可以看到 HTMX 把 ajax 變不見,只有單純的 html,使用 HTMX 才能算是真正意義上的把 "寫網頁" 這件事變簡單


由於只用上 html,所以很適合以模板引擎為基礎的後端服務框架 (SSR) 來使用,能夠提高使用者提驗及網站 SEO,這給後端工程師加速寫出網頁應用程式的方法,像 PHP、Python 等都很合適


在 HTMX 官網中的 "Server-Side Examples" 一節,提供了一些使用模板引擎的後端服務框架,讓後端工程師可以參考,這裡列出幾個常見的後端服務框架:


▸JavaScript Node.js

▸C# ASP.NET Core

▸C# Blazor

▸PHP Laravel

▸Python Django

▸Python FastAPI

▸Delphi WebBroker

▸FreePascal with Pas2JS

2025/03/14

程式碼轉換不再難:AI 助你精通 FastReport FastScript

 作者:吳祐賓

 

 


 

 

 

FastScript 是 FastReport VCL / FMX 超棒的產品之一,FastScript 功能非常豐富,七成需求都可以依賴它來完成,不一定要使用肥大的開發工具。

 

FastScript 限制

 

FastScript 也有其限制,例如它不支援以下兩種東西:

  • Class 建立。無法使用 record, class 等 object 來建立自己的物件
  • Set 集合。無法使用 [fsBold, ...]來設定集合

 

使用 AI,在 FastScript 上寫出一手好程式


還好現在有 AI 工具,把提示語給 AI 後,就可以快速寫出正確又有效率的工具出來,真是太棒了!

 

我使用提示語如下,提供給各位讀者參考:


使用 FastReport FastScript 的 JScript 語法建立一個腳本:[
  將 "Hello, World!" 顯示在 Memo1 元件中。(可以代換為你的需求)
]
Refer to the following FastScript JScript syntax rules:[
===
Classes
You cannot define a class inside the script, but you can use the external classes defined in add-in modules or your application.
===
Functions
There is a rich set of standard functions which can be used in a
script. To get an access to these functions, pass the fsGlobalUnit
reference to the TfsScript.Parent property.
===
About FastReport's FastScript:JScript syntax:
Program -> Statements
Statements -> Statement...
Block -> '{' Statements '}'
ImportStmt -> IMPORT (String/,)...
VarStmt -> VAR (VarDecl/',')...
VarDecl -> Ident [Array] [InitValue]Array -> '[' (ArrayDim/',')... ']'
ArrayDim -> Expression
InitValue -> '=' Expression
Expression -> SimpleExpression [RelOp SimpleExpression]...
SimpleExpression -> ['-'] Term [AddOp Term]...
Term -> Factor [MulOp Factor]...
Factor -> Designator
-> UnsignedNumber
-> String
-> '(' Expression ')'
-> '!' Factor
-> '[' SetConstructor ']'
-> NewOperator
-> '<' FRString '>'
SetConstructor -> SetNode/','...
SetNode -> Expression ['..' Expression]
NewOperator -> NEW Designator
RelOp -> '>'
-> '<'
-> '<='
-> '>='
-> '!='
-> '=='
-> IN
-> IS
AddOp -> '+'
-> '-'
-> '||'
-> '^'
MulOp -> '*'
-> '/'
-> '%'
-> '&&'
-> '<<'
-> '>>'
Designator -> ['&'] Ident ['.' Ident | '[' ExprList ']' | '(' [ExprList] ')']...
ExprList -> Expression/','...
Statement -> (AssignStmt | CallStmt | BreakStmt | ContinueStmt |
DeleteStmt | DoWhileStmt | ForStmt | FunctionStmt |
IfStmt | ImportStmt | ReturnStmt | SwitchStmt |
VarStmt | WhileStmt | WithStmt | Block) [';']
BreakStmt -> BREAK
ContinueStmt -> CONTINUE
DeleteStmt -> DELETE Designator
AssignStmt -> Designator ['+'|'-'|'*'|'/']'=' Expression
CallStmt -> Designator ['+''+'|'-''-']
ReturnStmt -> RETURN [Expression]
IfStmt -> IF '(' Expression ')' Statement [ELSE Statement]
SwitchStmt -> SWITCH '(' Expression ')' '{' (CaseSelector)... [DEFAULT ':' Statement] '}'
CaseSelector -> CASE SetConstructor ':' Statement
DoWhileStmt -> DO Statement [';'] WHILE '(' Expression ')' ';'
WhileStmt -> WHILE '(' Expression ')' Statement
ForStmt -> FOR '(' ForStmtItem ';' Expression ';' ForStmtItem ')' Statement
ForStmtItem -> AssignStmt-> CallStmt-> VarStmt-> Empty
TryStmt -> TRY CompoundStmt (FINALLY | EXCEPT) CompoundStmt
FunctionStmt -> FunctionHeading Block
FunctionHeading -> FUNCTION Ident FormalParameters
FormalParameters -> '(' [FormalParam/','...] ')'
FormalParam -> ['&'] Ident
WithStmt -> WITH '(' Designator ')' Statement
]
Specific usage of FastScript JScript: [
#language JScript // this is optional
import "unit1.js", "unit2.js"
Report.Memo1.Text = "HelloWorld"; // Sets the text of Memo1 through the Report object.
Memo1.Text = "HelloWorld";// Equivalent if Memo1 is in the main report scope (not within a sub-component like a Group or Band).
var mbSet = mbYes + mbNo;  // FastScript does not support sets directly; use addition for combining flags.
if (MessageDlg("Welcome to my JScript application. Exit now?", mtConfirmation, mbSet, 0) == mrYes)
{ ShowMessage("OK");}
// import section - must be before any other sections
var i, j = 0; // var section
function p1() // procedures and function
{//
}
// main procedure that will be executed.
p1();
// Dynamic array create. (var myarray = [] not support. Need use the FR TfrxArray Component)
var myarray = new TfrxArray();
myarray[0] = 1;
myarray[1] = 2;
for (i = 0; i < 1; i++) j = i + 1; myarray[i] = myarray[i] + j;
ShowMessage(IntToStr(myarray.Count)); // TfrxArray is not JScript array type.
// You can use the TStrings/TStringList. Same the Delphi TStrings/TStringList.
var myList = new TStringList();
myList.Add("message=HelloWorld");
ShowMessage(myList.Values("message"));
]
The following FastScript built-in functions are available:
function IntToStr(i: Integer): Stringfunction FloatToStr(e: Extended): Stringfunction DateToStr(e: Extended): Stringfunction TimeToStr(e: Extended): Stringfunction DateTimeToStr(e: Extended): Stringfunction VarToStr(v: Variant): Stringfunction StrToInt(s: String): Integerfunction StrToFloat(s: String): Extendedfunction StrToDate(s: String): Extendedfunction StrToTime(s: String): Extendedfunction StrToDateTime(s: String): Extendedfunction Format(Fmt: String; Args: array): Stringfunction FormatFloat(Fmt: String; Value: Extended): Stringfunction FormatDateTime(Fmt: String; DateTime: TDateTime): Stringfunction FormatMaskText(EditMask: string; Value: string): stringfunction EncodeDate(Year, Month, Day: Word): TDateTimeprocedure DecodeDate(Date: TDateTime; var Year, Month, Day: Word)function EncodeTime(Hour, Min, Sec, MSec: Word): TDateTimeprocedure DecodeTime(Time: TDateTime; var Hour, Min, Sec, MSec: Word)function Date: TDateTimefunction Time: TDateTimefunction Now: TDateTimefunction DayOfWeek(aDate: DateTime): Integerfunction IsLeapYear(Year: Word): Booleanfunction DaysInMonth(nYear, nMonth: Integer): Integerfunction Length(s: String): Integerfunction Copy(s: String; from, count: Integer): Stringfunction Pos(substr, s: String): Integerprocedure Delete(var s: String; from, count: Integer): Stringprocedure Insert(s: String; var s2: String; pos: Integer): Stringfunction Uppercase(s: String): Stringfunction Lowercase(s: String): Stringfunction Trim(s: String): Stringfunction NameCase(s: String): Stringfunction CompareText(s, s1: String): Integerfunction Chr(i: Integer): Charfunction Ord(ch: Char): Integerprocedure SetLength(var S: String; L: Integer)function Round(e: Extended): Integerfunction Trunc(e: Extended): Integerfunction Int(e: Extended): Integerfunction Frac(X: Extended): Extendedfunction Sqrt(e: Extended): Extendedfunction Abs(e: Extended): Extendedfunction Sin(e: Extended): Extendedfunction Cos(e: Extended): Extendedfunction ArcTan(X: Extended): Extendedfunction Tan(X: Extended): Extendedfunction Exp(X: Extended): Extendedfunction Ln(X: Extended): Extendedfunction Pi: Extendedprocedure Inc(var i: Integer; incr: Integer = 1)procedure Dec(var i: Integer; decr: Integer = 1)procedure RaiseException(Param: String)procedure ShowMessage(Msg: Variant)procedure Randomizefunction Random: Extendedfunction ValidInt(cInt: String): Booleanfunction ValidFloat(cFlt: String): Booleanfunction ValidDate(cDate: String): Booleanfunction CreateOleObject(ClassName: String): Variantfunction VarArrayCreate(Bounds: Array; Typ: Integer): Variant


前面的提示詞也可以使用:

 

將以下 C# 程式碼片段轉換為使用 JScript 語法的 FastReport FastScript。 該腳本應在 FastReport 環境中實現等效的功能。 請密切注意所提供的 FastScript JScript 語法規則和可用的內建函數。


總結

有了AI後,程式轉換上變得十分容易,從以前要求 Google 關鍵字力,現在要漸漸改為 AI 提示詞力。

 

如果有什麼想回饋的內容,歡迎在底下留言讓我知道。

2025/03/07

C++ Builder記憶體管理:使用unique_ptr實現物件自動釋放

作者:吳祐賓

 

 

據說,未來是個沒有 Delete 的世界(大誤)



 

C++ Builder開發者:硬核技術與直覺UI的雙重追求

 

在說明物件釋放方法之前,還得先聊聊 C++ Builder 開發者的習慣

 

就目前所接觸到會使用 C++ Builder 的開發者所整理的經驗,他們使用 C++ Builder 的理由大致上是以下兩點:


  1. 本身具有極高的 C 語言造詣,通常具有硬體開發經驗
  2. 和 Visual Studio C++ 相比,C++ Builder 的 UI 更是直覺的建立


 

由 1 可知,會使用 C 語言的開發者,通常有很強烈的語言潔癖,以及有自己一套對記憶體控制的要求

 

由 2 可知,C++ Builder 對從 C 過來的開發者來說,是很棒的 UI 建模工具



C++ Builder (CB) 中的物件釋放:常見問題與解決方案探討

 
只是,由於 CB 的 WinForm 是建構在 VCL framework 之上,所以學習 C++ 的物件自然是必須要的。在擴充C++的基本知識後,底下便是常見的寫法:

2025/03/06

node.js 的字串拼接函式比較

 作者:吳祐賓

 

 

 


 

 

最近在寫 Express.js API 時,被 AI 提示說直接字串拼接較不安全,建議使用指令處理。

 

但採用後原來的程式反而無法執行。檢查後才發現原來組合出來的路徑和我想的不一樣。

 

於是就做了一點測試比較:

 

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
// node.js 的字串拼接函式比較:

// 1. 字串直接拼接
console.log('/A/B/C' + '/fetch_emp');
// 輸出 (所有系統, 但 Windows 上可能不正確): /A/B/C/fetch_emp
// 解釋:  直接使用 + 運算子。在 *nix (Linux/macOS) 上沒問題,但在 Windows 上可能因路徑分隔符 (\) 而出錯。不推薦。

// 2. path.join() - 跨平台安全 (推薦!)
console.log(path.join('/A/B/C', '/fetch_emp'));
// 輸出 (Windows): \A\B\C\fetch_emp
// 輸出 (Linux/macOS): /A/B/C/fetch_emp  (與上面相同,但更可靠)
// 解釋:  path.join() 根據 *當前作業系統* 自動選擇正確的分隔符。這是最安全、可靠的方法,確保跨平台兼容。

// 3. path.posix.join() - 單純傳入virtualDirPath
console.log(path.posix.join('/A/B/C'));
// 輸出 (所有系統): /A/B/C
// 解釋: 只傳入一個參數時,作用等同於 path.normalize(),用來規範化路徑。

// 4. path.posix.join() + ./  (當前目錄)
console.log(path.posix.join('/A/B/C', './'));
// 輸出 (所有系統): /A/B/C/
// 解釋:  path.posix.join() 強制使用 POSIX 分隔符 (/)。'./' 代表當前目錄,被絕對路徑 /A/B/C 吸收,最後加上/。

// 5. path.posix.join() + / (根目錄)
console.log(path.posix.join('/A/B/C', '/'));
// 輸出 (所有系統): /A/B/C/
// 解釋:  path.posix 使用 / 分隔符。'/' 代表根目錄。因 /A/B/C 已是完整路徑, 最終結果為/A/B/C加上/,並處理掉多餘的 /

// 6. path.posix.join() + /fetch_emp (強制 POSIX)
console.log(path.posix.join('/A/B/C', '/fetch_emp'));
// 輸出 (所有系統): /A/B/C/fetch_emp
// 解釋:  path.posix.join() 強制 / 分隔符。即使 '/fetch_emp' 開頭是 /,也會正確處理,避免雙斜線。