2026/11/3 22:13:08

Delphi 12 HTMX与WebStencils:零基础实现网页局部交互

Delphi 12 HTMX与WebStencils:零基础实现网页局部交互 简介这份PDF面向Delphi开发者尤其适合使用RAD Studio 12、希望以传统HTML思维快速构建动态Web应用的中高级程序员它系统讲解HTMX与WebStencils两项技术的配合使用帮助读者摆脱繁琐的JavaScript与Ajax手写调用用简洁的HTML属性完成异步请求与局部刷新并借助WebBroker、WebStencils模板快速搭建页面原型。资源包仅1个PDF文件约919KB内容涵盖HTMX基本概念、与JavaScript/AJAX的对比、hx-get与hx-post请求写法、hx-swap内容交换控制以及WebBroker架构、请求响应处理等章节结构按由浅入深编排便于对照查阅。目前已有479人学习。读者可从中获得一套可落地的服务端驱动交互思路把页面逻辑上移到服务器端减少客户端脚本依赖使交互更直观可预测并掌握用预设HTML片段快速拼装界面的方法适合需要快速开发与迭代的Delphi Web项目参考。1. 先弄明白这份资源是什么在 HTML 标签上写几行属性网页就能动起来很多人写惯了桌面程序一想到做网页就犯怵HTML、CSS、JavaScript、前端框架一层叠一层光让一个按钮点下去换块内容就要写一串脚本、拼一堆字符串。这份 Delphi 12 的 HTMX WebStencils 中文版资料讲的正是绕开这套复杂流程的做法。HTMX 是一套前端写法你在现有的 HTML 标签上加上hx-get、hx-target这样的属性页面就能向服务器发起请求并把返回的一小段 HTML 替换到指定位置全程不写 JavaScriptWebStencils 是配套的模板引擎用开头的语法把数据和页面结构拼起来让服务器端生成 HTML 变得干净可维护。两者加上 RAD Studio 里的 WebBroker构成一条从零到能跑通网页的完整路径。适合会一点 Delphi、想往 Web 走但被前端吓退的人也适合完全零基础、想看清网页交互到底怎么发生的读者。2. 为什么零基础也值得学HTMX 把前端复杂度简化了很多2.1 前端那套麻烦事究竟麻烦在哪先看一个再普通不过的需求列表下面有个“加载更多”按钮点一下追加十条数据。如果用传统 JavaScript 加法 AJAX 的方式写代码大致要经历这些环节拿到按钮元素、绑定点击事件、点击瞬间把按钮置灰防止连点、发起请求、等待返回、判断状态码是不是 200、把返回的 JSON 解析成对象、遍历对象拼出一段 HTML 字符串、找到列表容器、把字符串塞进去、恢复按钮、出错了还要弹个提示。这还没算 loading 动画和重复点击的边界情况。这段代码本身不难难的是它散落在各处。一个页面上有五个这样的交互就有五份长得差不多的脚本页面结构一改脚本里的选择器跟着失效接口字段名调整前端要同步改。很多人第一次做 Web 项目时间不是花在业务逻辑上而是花在“为什么这个按钮点了两次就发出两个请求”这种问题上。这些经验只能自己摸索积累别人讲一遍你也记不住。更麻烦的是排查问题时的视角切换。数据不对你要先判断是前端没发对、后端没收到还是收到了但返回错了。前端脚本和后端代码分属两套语言、两个调试器出问题时经常要在两边来回跳。2.2 一句话记住 HTMX操作写在标签上拿回来的是 HTMLHTMX 的思路非常直接把“发请求”和“换内容”这两件事变成 HTML 标签上的属性。你不用写document.querySelector不用写fetch不用拼字符串浏览器解析到这个属性就知道该在什么时候、向哪个地址、发什么请求以及拿到结果之后放到哪。关键差别在于返回的内容。传统接口返回 JSON前端拿到数据后自己负责渲染HTMX 期望服务器直接返回一段现成的 HTML 片段浏览器把这段片段塞进目标元素里就完事。渲染这件事回到了服务器端而服务器端正好是你熟悉的地方——Delphi 代码加上模板。这个转变带来的直接好处是页面上任何一个局部的变化从数据到 HTML 的整条链路都在同一门语言、同一个项目里。哪里显示不对你去改那个返回片段的处理程序就行不用两头对。2.3 四个核心动作触发、目标、交换、附带数据把这套写法拆开其实只有四个概念需要记住下面这张表建议先抄下来贴在显示器边上。属性作用常见写法hx-get用 GET 方式向服务器要内容hx-get/todo/listhx-post用 POST 方式提交数据hx-post/todo/addhx-target返回的内容放到哪个元素里hx-target#todo-listhx-swap用哪种方式替换目标元素innerHTML、outerHTML、beforeendhx-trigger什么时候触发这次请求click、change、every 2shx-vals请求里额外带上的数据hx-vals{id:3}hx-target的取值是 CSS 选择器#todo-list表示 id 为todo-list的那个元素.card表示所有 class 为card的元素。hx-swap的默认值是innerHTML意思是把目标元素内部的内容整体换掉元素本身保留改成outerHTML就把元素本身也换掉改成beforeend就是追加到末尾做“加载更多”时最常用。这四个概念组合起来就能覆盖绝大多数页面交互按钮点击取数据、表单提交存数据、输入框变化时实时过滤、下拉框切换时刷新列表。资源里还提到hx-put、hx-delete这类语义化的请求方式以及hx-select从返回的整页里挑一段出来用属于进阶内容第一遍读的时候可以先跳过。2.4 为什么这套路子对 Delphi 开发者格外友好Delphi 开发者长期待在一个“事件驱动 可视化设计”的环境里思考方式天然贴合这套模型用户点了按钮触发一段代码代码处理完给出结果。HTMX 把这个模型搬到了浏览器上只不过“事件”变成了 HTML 属性“结果”变成了一段 HTML 文本。服务器端那边资源里用的是 WebBroker。它是 RAD Studio 自带的 Web 框架核心就三个东西请求对象、响应对象、处理程序。你在 WebModule 上放一个动作项写一个事件处理函数往Response.Content里写内容一个接口就有了。不需要额外装框架不需要配一堆中间件也不需要理解路由注册那一套。我第一次用这种方式做页面时最意外的是调试体验。以前查一个前端问题要开浏览器开发者工具、看网络请求、打断点看变量现在大部分问题直接在 Delphi 的调试器里就能定位因为生成 HTML 的那段代码就在手边断点打上去Response.Content里到底是什么一目了然不用再靠猜。3. 第一个例子怎么跑在 RAD Studio 里做出一个会回话的按钮3.1 准备从零建一个 WebBroker 项目整个过程在 RAD Studio 里完成不需要命令行。打开 IDE 之后按下面的顺序走菜单里选File→New→Other打开新建对话框。左侧分类里找到Web相关的一项选中Web Server Application。向导里选择Stand-alone console application也就是独立控制台程序这种方式不需要额外配置 Web 服务器最省事。端口填空闲的比如8080别和系统里其他服务撞上。完成后 IDE 会生成一个WebModule单元里面默认有一个处理所有请求的动作项。建好之后先别急着写业务直接运行一次。控制台窗口里会出现一行类似“正在监听 8080 端口”的提示打开浏览器输入http://localhost:8080能看到默认页面就说明环境通了。这一步看着不起眼但它把后面最容易卡住的环境问题提前排掉了。踩坑经验告诉我新手第一次往往会把代码写得没问题、却卡在端口被占用或者防火墙弹窗上先跑通空的比什么都重要。3.2 第一个处理程序让浏览器收到一段自己写的 HTML在WebModule上双击默认的那个动作项IDE 会跳到一个空白的事件处理函数里。在这里写下面这段// 处理 /hello 路径的请求返回一小段 HTML procedure TWebModule1.WebModule1HelloAction(Sender: TObject; Request: TWebRequest; Response: TWebResponse; var Handled: Boolean); begin // 一定要带 charset否则浏览器可能按 GBK 解析中文变乱码 Response.ContentType : text/html; charsetutf-8; Response.Content : p你好我是服务器返回的一小段 HTML/p; Handled : True; // 告诉框架这个请求我处理完了别再往下传 end;这段代码只有一个真正的逻辑往响应的内容属性里写一段字符串。ContentType是告诉浏览器“我给你的是一段 HTML编码是 UTF-8”后半句很重要中文乱码十有八九是这里没写。Handled这个参数容易被忽略。WebBroker 会把请求依次交给动作项处理Handled : True表示已经处理完毕后面的动作项不用再管不写的话可能出现同一请求被处理两次、返回内容拼接在一起的情况现象是页面重复出现两遍内容。回到 IDE 里选中这个动作项把它的PathInfo属性设成/hello。再运行时访问http://localhost:8080/hello浏览器上就会显示你写的那句话。这是整个学习路径上的第一个里程碑——服务器端返回的 HTML 真的出现在浏览器里了。3.3 加上 HTMX一个按钮点下去只换页面里的一小块接下来让页面“动”起来。先准备 htmx 的脚本文件从 HTMX 官方渠道下载htmx.min.js放到程序可执行文件所在目录下一个名叫wwwroot的文件夹里。然后在项目里加一个动作项路径设成/static用来把静态文件送出去// 把 wwwroot 目录下的文件按请求路径读出来返回 procedure TWebModule1.WebModule1StaticAction(Sender: TObject; Request: TWebRequest; Response: TWebResponse; var Handled: Boolean); var LocalPath: string; begin // 请求路径里的 / 换成系统目录分隔符拼成磁盘上的真实路径 LocalPath : ExtractFilePath(ParamStr(0)) wwwroot StringReplace(Request.PathInfo, /, PathDelim, [rfReplaceAll]); if FileExists(LocalPath) then Response.ContentStream : TFileStream.Create(LocalPath, fmOpenRead or fmShareDenyWrite) else begin Response.StatusCode : 404; Response.Content : not found; end; Handled : True; end;ParamStr(0)是当前可执行文件的完整路径ExtractFilePath取出它所在的目录后面拼上wwwroot和请求路径就得到了文件在磁盘上的位置。fmShareDenyWrite表示允许别人同时读、但不允许写避免多个请求同时打开同一个文件出问题。这段代码没有做路径穿越的防护入门阶段够用等你要放到公网上时一定要补上这一点在避坑那一章会再说。现在写页面。新增一个动作项路径/demo返回下面这段 HTML!DOCTYPE html html langzh-CN head meta charsetutf-8 !-- 引用刚放好的本地脚本不依赖外网 -- script src/static/htmx.min.js/script /head body h2第一个 HTMX 例子/h2 button hx-get/hello hx-target#result hx-swapinnerHTML 点我一下 /button !-- 服务器返回的内容会进到这个容器里 -- div idresult这里会显示服务器返回的内容/div /body /html三个属性的分工很明确hx-get/hello说明点下去要往/hello要内容hx-target#result说明拿回来的东西放进 id 为result的容器hx-swapinnerHTML说明替换容器内部的内容容器本身保留。访问http://localhost:8080/demo点一下按钮下面那行占位文字就会被替换成 3.2 里写的那句“你好我是服务器返回的一小段 HTML”而页面其他部分纹丝不动。3.4 逐行解释一次点击背后发生了什么点下去的那一瞬间浏览器做了这几件事脚本监听到按钮的点击事件读这个元素上的hx-get属性知道要发一个 GET 请求到/hello。请求发出去走的是浏览器原生的网络通道和你在地址栏敲回车没有本质区别。WebBroker 收到请求按路径匹配到/hello这个动作项调用 3.2 里那个函数。函数把一段 HTML 字符串写进响应Handled : True结束处理。浏览器拿到响应内容按hx-target找到#result元素按hx-swap的方式把内容替换进去。整个过程没有任何一行 JavaScript 是你写的。这解释了为什么这套方式对新手友好你要关心的事情只有两件——服务器返回什么、返回的东西放到哪。至于“什么时候发”“怎么发”“怎么塞”属性已经替你说清楚了。这里有个思维习惯需要调整。以前你可能觉得“局部刷新”是很高级的技术其实本质就是“服务器给我一段 HTML我把它放进某个盒子”。盒子在哪、放进去还是替换掉由属性决定。想明白这一点后面所有交互都是这个模式的变体。3.5 再进一步写一个能添加的待办光有按钮还不够试一个带表单的。再加一个动作项/todo/add返回一小段列表项// 接收表单提交的标题返回一个列表项片段 procedure TWebModule1.WebModule1TodoAddAction(Sender: TObject; Request: TWebRequest; Response: TWebResponse; var Handled: Boolean); var Title: string; begin // 表单字段名是 title取不到就给个默认值防止空着 Title : Request.ContentFields.Values[title]; if Title then Title : 没有填写内容; Response.ContentType : text/html; charsetutf-8; // 拼接时对用户输入做最基本的转义防止标签被当成 HTML 执行 Response.Content : li StringReplace(Title, , lt;, [rfReplaceAll]) /li; Handled : True; end;Request.ContentFields是表单提交过来的字段集合Values[title]按字段名取值取不到返回空字符串。这个变量里的内容来自用户直接拼进 HTML 是有风险的所以代码里做了一次的替换这是最小化的处理方式正式项目里更稳妥的做法是交给模板引擎自动转义。页面上这么写form hx-post/todo/add hx-target#todo-list hx-swapbeforeend input typetext nametitle placeholder今天要做什么 button typesubmit添加/button /form ul idtodo-list li这是原本就有的第一条/li /ul注意hx-swap换成了beforeend意思是把返回的li追加到列表末尾而不是覆盖整个列表。这就是前面说的“追加型交互”做消息流、评论列表、日志输出全都用这一招。跑到这一步你已经把“GET 取内容”“POST 提交数据”“替换”“追加”四种最常见的动作都试过一遍了。剩下的复杂交互基本都是在这几个动作上换参数。4. 模板怎么写WebStencils 的 符号、条件和循环4.1 为什么不再用字符串拼接 符号的角色3.5 里那段li Title /li短的时候没什么感觉一旦页面长起来就是灾难标签和引号混在一起、条件判断要靠 if 拼字符串、改一个样式要翻半天、少写一个闭合标签浏览器还照样显示导致错误非常隐蔽。WebStencils 解决的就是这个问题。它让你把页面写成一个普通的 HTML 文件需要插入数据的地方用开头标记运行时由引擎把值填进去。页面结构和代码逻辑因此分开.html文件管长什么样.pas文件管取什么数据。这个概念用一句话概括模板是一个带空缺的 HTML就是那个空缺的标记。你不需要记很多语法日常用到的就是“取值”“判断”“循环”三类。4.2 变量、点表示法和注释最常用的三件事取值最基础的写法是加变量名。如果数据是一个对象可以用点表示法往里取比如Model.Title表示取Model这个对象的Title属性Model.Items表示取它的Items集合。这种写法和 Delphi 里访问对象属性的习惯一致不需要额外记一套语法。有一点新手常犯模板里的后面跟的名字必须和你在代码里注册进去的名字一致大小写敏感。资源里的示例用的是Model.xxx这种形式你自己写的时候如果注册的是TodoList模板里就得写TodoList写成Todolist会取不到值页面上直接显示空白还不报错——这种问题排查起来最容易浪费时间。注释的写法是*开头、*结尾中间的整段内容不会输出到最终页面。调试模板时我习惯把暂时不用的块用注释包起来比反复删改安全改坏了还能还原。4.3 条件与循环让列表自己长出来条件判断的写法接近 Delphi 的语法结构大致是这样的形式if (Model.Count 0) begin p当前共有 Model.Count 条记录/p end else begin p还没有任何记录先添加一条吧/p end;条件为真时输出上面那段为假时输出下面那段。资源里还提到ifnot这样的反向写法适合“条件不成立时提示”的场景比如登录状态检查。循环用来渲染列表这是模板最常处理的场景ul ForEach (var item in Model.Items) begin liitem.Name/li end; /ulModel.Items是一个集合循环体每次取一个元素元素内部的属性用点表示法取。这段模板不管你传进来三条还是三百条都会自动展开成对应数量的li你不需要在代码里写循环拼字符串也不需要处理末尾多出来的逗号或标签。提示这类循环语法在不同小版本的模板引擎里可能略有差异比如花括号和begin...end两种写法都见过。资源里对语法有逐条说明动手前先照着那份语法写跑通之后再改成自己顺手的风格别一开始就凭印象硬写。4.4 布局复用LayoutPage、RenderBody 和 Import页面一多重复的问题就来了每个页面都要写head、导航栏、页脚。这些结构如果每个文件各写一遍改一次站名要改二十个文件。WebStencils 提供的解决方式是布局页。你写一个_Layout.html作为外层骨架在要放具体页面内容的位置写RenderBody然后在子页面顶部用LayoutPage _Layout.html声明“我套用这个骨架”。运行时引擎会把子页面的内容填进RenderBody的位置拼成完整页面。Import用来引入可复用的片段比如把导航栏、侧边栏、卡片这些反复出现的块单独存成文件在需要的地方引入。ExtraHeader配合RenderTitle可以处理“父布局里有默认标题个别页面想换一个”的情况。这套组合的价值在你写到第五六个页面时才会明显感觉到。我个人的做法是第一个页面老老实实写完整 HTML等第二个页面出现大量重复时再抽布局抽早了容易过度设计抽晚了改动成本高。资源里给了标准布局、标题正文分页、可复用组件几种模板模式第一遍可以先看标准布局其余等有需要再翻。4.5 把模板接进 WebBroker绑定与数据传递模板写好了得让 WebBroker 知道去哪找它、数据从哪来。资源里列出的组件包括模板引擎、模板处理器、Web 服务器引擎这几类作用是把模板文件读进来、把变量注册进去、把生成的 HTML 交给响应。典型的流程是创建模板引擎实例、设置模板所在目录、注册要用的变量、执行渲染、把结果写进Response.Content。顺序上有一点值得提醒变量要在渲染之前注册好渲染之后再注册就没用了页面上该显示的地方全是空白。这类问题现象很统一——页面结构出来了但所有位置都是空的。遇到这种情况先回头检查注册代码在不在渲染语句前面其次检查名字拼写最后再怀疑模板文件路径。5. 避坑清单新手最常出错的五个地方5.1 点了没反应页面一动不动现象按钮上属性都写了点下去什么都不发生浏览器地址栏也没变页面看上去完全没动。原因最常见的是 htmx 脚本根本没加载成功。路径写错、文件没放进wwwroot、静态文件的动作项路径没对上都会导致脚本 404。脚本不在属性就只是普通字符串没人理会它。其次是属性名拼错比如写成hx-tartget脚本扫不到就跳过。解决打开浏览器开发者工具的网络面板刷新页面看htmx.min.js那条请求的状态码是不是 200。不是 200 就先修静态文件路径先别怀疑业务代码。是 200 还不动就在元素上右键检查看属性名有没有拼错、有没有被其他脚本覆盖掉。5.2 只想换一小块结果整页都塞进来了现象点按钮之后目标区域里出现了一整个页面html、head全塞进去了样式也乱了。原因服务器返回的是完整页面而hx-target指向的是页面里的一个小容器。htmx 默认会把返回内容的全部塞进去它并不知道你只想要中间那一段。解决两个方向。要么让服务器端根据请求返回片段而不是整页可以在请求头里判断是不是 htmx 发来的是就只输出局部要么在元素上加hx-select#todo-list让脚本从返回的整页里挑出指定元素再用。前者更干净后者改动更小看你的项目处在什么阶段。5.3 模板找不到或者中文全变成乱码现象一种是启动就报找不到模板文件另一种是页面出来了中文全是问号或者方块。原因路径问题多半是模板目录用了相对路径程序的工作目录和你以为的不一样调试时对、部署后就错。乱码基本是响应头里的编码没写或者模板文件本身的保存编码与声明不一致。解决模板目录统一用ExtractFilePath(ParamStr(0))拼绝对路径别依赖工作目录。编码上两头对齐响应头写text/html; charsetutf-8模板文件本身也存成 UTF-8。这两处只要有一处是别的编码中文就会出问题而且报错信息往往误导你去查别的地方。5.4 表单提交被拦下或者用户输入把页面搞坏现象表单提交后没有预期结果或者某条数据一显示出来整个页面布局就崩了甚至弹出奇怪的提示。原因前者是跨站请求伪造防护在起作用。资源里专门讲了 CSRF 保护服务器会校验请求来源的合法性缺少令牌或者来源不对的请求会被拒绝。后者是跨站脚本问题用户输入里带了 HTML 标签被原样输出到了页面上浏览器把它当成结构执行了。解决CSRF 这块把模板里生成的令牌字段原样带上别手动拼请求如果用的是表单提交确认表单里有那个隐藏字段。XSS 这块输出用户内容时一定要走转义模板引擎通常默认转义但你手动拼字符串的地方是绕过转义的凡是拼字符串的地方都要过一遍。资源里对数据校验和安全注意事项有专门说明第一遍读的时候容易跳过建议至少把这一节看完。5.5 本机跑得好好的换个环境就崩现象开发机上一切正常换台机器或者换个端口部署页面打不开或者静态资源全丢。原因端口被占用、防火墙拦截、路径写死、静态文件没跟着一起拷过去这几种情况占了绝大多数。特别是wwwroot目录很多人只拷了可执行文件忘了拷资源目录。解决部署前对一遍清单——端口是否可用、可执行文件同目录下的资源目录是否完整、路径是否都是运行时拼出来的而不是写死的绝对路径。养成这个习惯之后这类问题基本不会再找上门。6. 下一步学什么从待办清单走到一个能交付的小系统跑通按钮和表单之后很多人会卡在“接下来做什么”。这里给一条我自己走过、也觉得最省力的路径不要急着看高级特性先把一个待办清单从头到尾做完整再回头补知识点。具体顺序建议是这样。第一步把 3.5 那个只能追加的列表改成能存能删加一个删除按钮用hx-delete或者hx-post到删除接口返回更新后的整个列表片段hx-swap用outerHTML整个换掉。这一步会让你真正理解“返回片段”的粒度怎么选。第二步把列表换成模板渲染不再拼字符串数据用一个对象集合传进去用ForEach展开。第三步抽出布局页把导航和页脚放进_Layout.html体会RenderBody的作用。第四步加上分类和筛选用hx-trigger监听下拉框的change事件体验“不点按钮也能发请求”。阶段目标对应资源里的部分第一阶段增删改查全通WebBroker 基础、HTMX 请求方式第二阶段页面由模板生成模板语法、变量注册第三阶段多页面不重复布局、RenderBody、Import第四阶段条件渲染与筛选if、hx-trigger、hx-vals第五阶段能给别人用会话管理、CSRF、数据校验每个阶段做完都停下来把代码从头读一遍问自己两个问题这个请求是谁触发的、返回的内容去了哪里。这两个问题能答上来说明你真的理解了而不是照着抄。答不上来就回去看那一节不要往下走因为后面的东西都建立在这个模型上。资源里后半部分讲了Scaffolding、LoginRequired、OnValue事件处理、模板模式这些进阶内容第一遍读的时候完全可以跳过等你把上面五个阶段走完再回来翻会发现每一节都能对上你遇到的问题。这种“先做再读”的顺序比从头到尾读一遍效率高得多因为你有具体的问题要解决读的时候注意力会自动落在答案上。有个习惯我从第一次做这类项目一直保持到现在每加一个新交互先不看效果先把“请求发到哪、返回什么、放到哪”这三句话写下来再动手写代码。写不出来说明我还没想清楚那就先想清楚再写。这么做之后我因为“忘了设 target 导致整页乱塞”这类问题返工的次数少了很多。希望这些能帮到你从跑通第一个按钮开始一步一步来。本文还有配套的精品资源点击获取