专业编程基础技术教程

网站首页 > 基础教程 正文

强大 WebView2 + 不用写 JavaScript 的 htmx.js 「小轻快」开发桌面程序

ccvgpt 2024-11-26 00:54:29 基础教程 1 ℃

WebView2 是越来越香了。

WebView2 不但是 Win11 自带的系统组件,Win10 也已经自动推送安装。即使是少量没有安装 WebView2 的系统 —— 使用 aardio 中的 web.view 也会自动安装( 不需要写任何代码 )。

强大 WebView2 + 不用写 JavaScript 的 htmx.js 「小轻快」开发桌面程序

我用 WebView2 开发了很多项目,不得不说 WebView2 稳定可靠、性能强悍,接口简洁,是真的让人省心。

htmx.js

这里介绍一个适合用于 WebView2 的极简前端组件 htmx.js ,这个组件最大的特色就是简单,一学就会,也很容易理解。

我们正常浏览一个网页的过程是在浏览器里输入网址,向 HTTP 服务器发送请求。然后服务器返回 HTML 代码,浏览器显示页面。

但是 htmx.js 脑洞大开,让网页上的每一个 HTML 节点都可以向服务器发送请求并获取 HTML,并实时更新页面上指定的节点。而且不需要写任何 JavaScript 代码。

起步

首先我们打开 aardio ,创建 WebView2 工程并选择 htmx.js 模板:

生成的工程如下:

点『运行』可直接测试效果,点『发布』可生成独立 EXE 文件 。

在工程管理器中右键点『网页』弹出菜单,然后点『用外部编辑器打开』,如果安装了 VS Code 会使用 VS Code 打开网页目录。

在 VS Code 中点击并打开 index.html 源码:

htmx.js 基础

打开 index.html ,先看最简单的 htmx.js 示例:

<button hx-get="/api/index.aardio" 
    hx-swap="innerHTML" 
    hx-trigger="click" 
    hx-target="#info-div"  >
    点这里发送 GET 请求
</button><br>

<div id="info-div"></div>

注意看凡是 "hx-" 前缀的属性都是用于 htmx.js 。

hx-trigger 用于指定在什么事件发生时触发 HTTP 请求,例如:

hx-trigger="click" 

表示在 click 单击事件发生时触发请求。

hx-trigger 可使用标准网页事件名,常用事件如下:

  • load - 网页元素首次加载时触发请求
  • click - 单击时触发请求这是除表单,表单控件之外所有元素的默认事件
  • change - 控件值改变时触发请求。input,textarea,select 等控件的默认事件
  • submit - 提交表单时触发请求。表单的默认事件
  • keydown - 按键时触发请求
  • keyup - 放开按键时触发请求
  • mouseenter - 鼠标进入时触发请求
  • mouseleave - 鼠标离开时触发请求
  • every 时间 - 定时触发请求,例如 hx-trgger = "every 1s" 指定每隔 1 秒发送一次请求。

事件名后面还可以添加修饰器,例如修饰器 once 表示只允许触发一次 :

hx-trigger="click once" 

其他事件修饰器:

  • changed - 只有在元素的值更改时发出请求
  • delay:延时 - 在指定的延时后发出请求,例如 hx-trgger = "load delay:1s" 指定元素加载后延迟 1 秒发送请求。如果服务端不断地返回相同 HTML 并替换节点自身,也可以实现轮询的效果( aardio 后端可以控制何时停止轮询 )。
  • throttle:延时 - 节流,避免在指定时间内重复请求
  • from:CSS选择器 - 监听指定元素上的事件。

下面的 HTML 使用了多个事件修饰器:

<input type="text" 
  hx-trigger="keyup changed delay:500ms"
  hx-post="/api/index.aardio" >

这表示在按键放开( keyup ),文本框的内容发生改变( changed )时触发,并且延时 500 毫秒再发送请求。

hx-get 则指定要请求的是哪个后端页面,例如:

hx-get="/api/index.aardio" 

表示事件触发时,请求 "/api/index.aardio" 这个页面。因为 aardio 在启动 SPA 应用时自动指定了后端根目录为 "/web",所以实际请求的是 "/web/api/aardio" 。

而 hx-swap 则指定要将返回的 HTML 写入到哪里,"innerHTML" 指定是更新网页节点内部 HTML,"outerHTML" 指定替换目标网页节点的全部 HTML ,其他还有 "afterbegin" , "beforebegin" , "beforeend" , "afterend" , "none" 。这些看名字就知道是什么作用,就不解释了。

hx-target 属性用 CSS 选择器指定要写入的网页节点,例如:

 hx-target="#info-div" 

指定服务器返回的 HTML 写入 id 为 "info-div" 的节点。如果省略 hx-target 属性表示写入目标是当前节点自身。

hxmx.js 在更新 HTML 时,如果发现新旧 html 中有 id 相同的元素会进行优化并平滑显示。

看到这里,htmx.js 您已经会用了。

虽然 htmx.js 文档里有更多花式用法,但一般可能用不上。有些事搞太复杂了也不一定是好事。

htmx.js + aardio 后端

aardio 提供了嵌入式 HTTP 服务器,可以直接使用 aardio 代码写网页,支持与 PHP 类似的模板语法。

aardio 的模板语法很简单,aardio 代码写在 <? ?> 内部,而 HTML 代码写在 <? ?> 外部就可以了。实际上 <? ?>外部的代码被转换为了 aardio 中 print 函数的参数。

例如服务端有下面的 aardio 代码:

<span>abc</span>
<?
  response.write("123")
?>

运行后会自动转换为纯 aardio 代码如下:

print("<span>abc</span>");
response.write("123");

在 HTTP 后端中,print 函数实际上就是指向用于向 HTTP 客户端输出数据的 response.write() 函数。

在 HTTP 后端有两个最常用的对象,request 对象包含了所有 HTTP 请求信息,而 response 对象为 HTTP 响应对象,用于向客户端发送数据。

打开 aardio 自带「工具 > 库函数文档」,点击 fastcgi.client 的文档可以查看 request, response 对象的所有属性与方法。aardio 中的所有 HTTP 服务端实现都统一兼容 fastcgi.client 文档规定的 request, response 用法。

也可以参考 aardio 开始页的 《 aardio 网站开发、FastCGI开发入门教程 》。至于 aardio 模板语法,请参考 《 aardio 语法与使用手册 > aardio 语言 > 模板语法 》

aardio 的模板语法不仅仅可以用于写 HTTP 后端,也不仅仅是可以用于输出 HTML,实际上可以用于生成任何字符串。aardio 中的很多功能都支持这种模板语法,例如运行时编译 C# 代码就支持用 aardio 模板语法生成 C# 代码。另外 aardio 提供 string.loadcode() 函数可以直接解析 aardio 模板并返回字符串。

htmx.js指示动画,aardio 后端线程

这里要注意,上面范例工程默认导入的 HTTP 服务器是:

wsock.tcp.simpleHttpServer;

这是一个多线程的 HTTP 服务端,每次被请求执行的 aardio 代码都是在后台线程中运行。aardio 多线程开发要注意的是每个线程都运行在独立的环境,全局变量是相互隔离的,这个限制实际上让 aardio 的多线程开发更简洁,坑更少,具体请参考 aardio 自带「范例程序 > aardio 语言 > 多线程」。

如果改为 wsock.tcp.asynHttpServer 则是单线程异步的 HTTP 服务器。

下面我们仍然使用默认的 simpleHttpServer 。多线程的好处是耗时操作不会卡界面。后端在进行耗时操作时,网页前端通常需要显示一个动画,htmx.js 做这事就很简单。

我们只要简单的修改一下前面讲过的网页代码如下:

<button hx-get="/api/index.aardio" 
    hx-indicator="#indicator" >
    点这里发送 GET 请求
</button><br>

<img id="indicator" 
    class="htmx-indicator" 
    src="/images/loading.gif"/>

主要是增加了 hx-indicator 属性,该属性的值用一个 CSS 选择器指定了发送 HTTP 请求时要显示的 HTML 元素,这里指定的是 id 为 "indicator" 的元素。

实际上我们可以自定义这个请求动画的样式,我们打开样式文件 index.css 添加下面的样式:

.htmx-indicator{
    display:none;
}
.htmx-request.htmx-indicator{
    display:inline;
}

在发送请求时,网页上被设定的指示元素会自动添加 CSS 类 "htmx-request",HTTP 请求结束会移除该类。

然后我们打开对应的 aardio 后端代码 /web/api/index.aardio ,输入以下代码:

<span>
<?

if( request.method == "GET"){
    
    /*
    这是多线程后端,
    这里等 2 秒,网页会显示加载动画
    */
    sleep(2000);
    
    response.write( time() )    
} 
?></span>


上面的代码的作用是:如果收到 GET 请求,线程就休眠 2 秒以模拟耗时操作。然后输出当前时间。

我们运行一下看看效果:

请求参数

htmx.js 提交请求的节点如果是一个表单控件,只要指定 name 属性 —— 就会自动以该名字发送请求参数,参数值就是控件的值。

如果提交请求的节点是表单,则 HTTP 请求参数为表单内所有控件的值。

也可以在节点的 hx-vals 属性中用一个 JSON 对象指定请求参数,例如网页这样写:

<button hx-get="/api/index.aardio" 
    hx-vals='{"myVal": "值"}'>
    点这里发送 GET 请求
</button><br>

aardio 后端就可以使用:

request.get["myval"]

取到 HTTP 请求参数 myval 的值。

如果使用 POST 发送请求,例如:

<button hx-post="/api/index.aardio" 
    hx-vals='{"myVal": "值"}'>
    点这里发送 GET 请求
</button><br>

那么 aardio 后端可以使用

request.post["myval"]

取到 HTTP 请求参数 myval 的值。

在 aardio 后端使用:

request.query("myval")

可以取到 GET 或 POST 发送的 myval 参数值。

hx-vals 还可以通过加上 javascript: 或者 js: 前缀后使用 JS 对象返回请求参数,例如:

<button hx-get="/api/index.aardio"  
    hx-vals='javascript:{myVal: "值"}'    >
    点这里发送 GET 请求
</button> 

web.form 也玩 htmx.js

有趣的是 web.form 也可以支持 htmx.js 。

web.form 是基于系统自带的 IE 内核控件,注意系统虽然删除了 IE 浏览器,但 IE 控件属于系统组件,Windows 有说明该控件不会被移除。IE 控件的好处是从 XP 到 Win11 所有操作系统都自带。

而且 IE 控件很轻量,与本地程序交互的接口也非常方便。Win10 ,Win11 自带的是 IE11 内核,写写一般的网页还是很好用的。至于 Win7 —— 因为只有极低的份额,一般软件不用考虑。

htmx.js 最后一个支持 IE 11 的版本是 1.6.1 ,这个足够用了。前面说过我们需要的只是局部请求刷新的功能,其他功能我们用不上,所以追新无意义。

首先我们打开 aardio 工程向导,选择「 Web 界面 > Web Form 」然后创建工程即可,新版工程模板默认就是使用 htmx.js 。

其他 HTML 代码写法与前面介绍的 WebView2 基本一样。不过 web.form 本就支持 EXE 内嵌资源文件,所以默认并不会启动 HTTP 服务器,需要多写几句代码。

打开工程的 webPage.aardio 源码:

可以看到源码中是如下启动 HTTP 服务器的:

import web.form;
var wb  = web.form(winform); 

//多线程后端
import wsock.tcp.simpleHttpServer;
wsock.tcp.simpleHttpServer.documentBase = "\web"
var indexUrl = wsock.tcp.simpleHttpServer.startUrl("\index.html")
 
wb.go(indexUrl);

我并没有把这几句代码封装到 wb.go() 函数中。

有些新手总以为代码越少越好,其实并非如此,有时候多写几句更容易看清楚代码的思路,更容易理解我们正在使用的技术。

下面我们看下 web.form + htmx.js 范例的运行效果:

上面示例程序的主窗口是使用 win.ui.tabs 做的,只有其中一个标签页用到了网页。

其实一般桌面软件的界面并不是一定要全部使用网页实现。有时候我们将界面中适合用网页呈现的部分用网页做,可能会更好。

我们在使用任何技术时,都要考虑一下适不适合。没有一样技术能适合做所有的事,多个选择总是好事。

Tags:

最近发表
标签列表