更新其他内容

人们第一次使用 htmx 时经常会出现一个问题:

¥A question that often comes up when people are first working with htmx is:

“我需要更新屏幕上的其他内容。我该怎么做?“

¥“I need to update other content on the screen. How do I do this?”

有多种方法可以做到这一点,在这个例子中,我们将向你介绍其中的一些方法。

¥There are multiple ways to do so, and in this example we will walk you through some of them.

我们将使用以下基本 UI 来讨论这个概念:一个简单的联系人表,以及下面的表单,用于使用 hx-post 将新联系人添加到表中。

¥We’ll use the following basic UI to discuss this concept: a simple table of contacts, and a form below it to add new contacts to the table using hx-post.

<h2>Contacts</h2>


<table class="table">
  <thead>
    <tr>
      <th>名称</th>
      <th>电子邮件</th> 
      <th></th>
    </tr>
  </thead>
  <tbody id="contacts-table">
    ...
  </tbody>
</table>


<h2>Add A Contact</h2>
<form hx-post="/contacts">
  <label>
    Name
        <input name="name" type="text">  
  </label>
  <label>
    Email
        <input name="email" type="email">  
  </label>
</form>

这里的问题是,当你在表单中提交新联系人时,你希望上面的联系人表刷新并包含表单刚刚添加的联系人。

¥The problem here is that when you submit a new contact in the form, you want the contact table above to refresh and include the contact that was just added by the form.

我们有什么解决方案?

¥What solutions do we have?

解决方案 1:展开目标

¥Solution 1: Expand the Target

这里最简单的解决方案是将表单的 “扩展目标” 括起来,以包含表格和表单。例如,你可以将整个内容封装在 div 中,然后以以下形式定位该 div

¥The easiest solution here is to “expand the target” of the form to enclose both the table and the form. For example, you could wrap the whole thing in a div and then target that div in the form:

<div id="table-and-form">
    <h2>Contacts</h2>
    <table class="table">
      <thead>
        <tr>
          <th>Name</th>
          <th>Email</th>
          <th></th>
        </tr>
      </thead>
      <tbody id="contacts-table">
        ...
      </tbody>
    </table>
    <h2>Add A Contact</h2>
    <form hx-post="/contacts" hx-target="#table-and-form">
      <label>
        Name
            <input name="name" type="text">  
      </label>
      <label>
        Email
            <input name="email" type="email">  
      </label>
    </form>
</div>

请注意,我们使用 hx-target 属性定位封闭的 div。你需要在对 POST/contacts 的响应中同时渲染表格和表单。

¥Note that we are targeting the enclosing div using the hx-target attribute. You would need to render both the table and the form in the response to the POST to /contacts.

这是一种简单可靠的方法,尽管它可能感觉不是特别优雅。

¥This is a simple and reliable approach, although it might not feel particularly elegant.

解决方案 2:带外响应

¥Solution 2: Out of Band Responses

解决这个问题的更复杂的方法是使用 带外交换 将更新的内容交换到 DOM。

¥A more sophisticated approach to this problem would use out of band swaps to swap in updated content to the DOM.

使用这种方法,HTML 根本不需要从原始设置进行更改:

¥Using this approach, the HTML doesn’t need to change from the original setup at all:

<h2>Contacts</h2>


<table class="table">
  <thead>
    <tr>
      <th>名称</th>
      <th>电子邮件</th> 
      <th></th>
    </tr>
  </thead>
  <tbody id="contacts-table">
    ...
  </tbody>
</table>


<h2>Add A Contact</h2>
<form hx-post="/contacts">
  <label>
    Name
        <input name="name" type="text">  
  </label>
  <label>
    Email
        <input name="email" type="email">  
  </label>
</form>

你无需在前端修改某些内容,而是在对 POST/contacts 的响应中包含一些其他内容:

¥Instead of modifying something on the front end, in your response to the POST to /contacts you would include some additional content:

<tbody hx-swap-oob="beforeend:#contacts-table">
  <tr>
    <td>Joe Smith</td>
    <td>joe@smith.com</td>
  </tr>
</tbody>

<label>
  Name
      <input name="name" type="text">
</label>
<label>
  Email
      <input name="email" type="email">
</label>

此内容使用 hx-swap-oob 属性将自身附加到 #contacts-table,在成功添加联系人后更新表格。

¥This content uses the hx-swap-oob attribute to append itself to the #contacts-table, updating the table after a contact is added successfully.

解决方案 3:触发事件

¥Solution 3: Triggering Events

一种更复杂的方法是在成功创建联系人时触发客户端事件,然后在表上监听该事件,从而导致表刷新。

¥An even more sophisticated approach would be to trigger a client side event when a successful contact is created and then listen for that event on the table, causing the table to refresh.

<h2>Contacts</h2>


<table class="table">
  <thead>
    <tr>
      <th>名称</th>
      <th>电子邮件</th> 
      <th></th>
    </tr>
  </thead>
  <tbody id="contacts-table" hx-get="/contacts/table" hx-trigger="newContact from:body">
    ...
  </tbody>
</table>


<h2>Add A Contact</h2>
<form hx-post="/contacts">
  <label>
    Name
        <input name="name" type="text">  
  </label>
  <label>
    Email
        <input name="email" type="email">  
  </label>
</form>

我们添加了一个新的端点 /contacts/table,用于重新渲染联系人表。我们对此请求的触发器是自定义事件 newContact。我们在 body 上监听此事件,因为当它由对表单的响应触发时,由于事件冒泡,它最终会击中主体。

¥We have added a new end-point /contacts/table that re-renders the contacts table. Our trigger for this request is a custom event, newContact. We listen for this event on the body because when it is triggered by the response to the form, it will end up hitting the body due to event bubbling.

当在 POST 到 /contacts 期间成功创建联系人时,响应包含一个 HX-Trigger 响应标头,如下所示:

¥When a successful contact creation occurs during a POST to /contacts, the response includes an HX-Trigger response header that looks like this:

HX-Trigger:newContact

这将触发表格向 /contacts/table 发出 GET,这将渲染新添加的联系人行(除了表格的其余部分)。

¥This will trigger the table to issue a GET to /contacts/table and this will render the newly added contact row\ (in addition to the rest of the table.)

非常干净,事件驱动编程!

¥Very clean, event driven programming!

解决方案 4:使用路径依赖扩展

¥Solution 4: Using the Path Dependencies Extension

最后一种方法是使用 REST-ful 路径依赖来刷新表。Intercooler.js 是 htmx 的前身,已将 基于路径的依赖 集成到库中。

¥A final approach is to use REST-ful path dependencies to refresh the table. Intercooler.js, the predecessor to htmx, had path-based dependencies integrated into the library.

htmx 将其作为核心功能删除,但支持扩展 路径依赖,可为你提供类似功能。

¥htmx dropped this as a core feature, but supports an extension, path deps, that gives you similar functionality.

更新我们的示例以使用扩展将涉及加载扩展 javascript,然后像这样注释我们的 HTML:

¥Updating our example to use the extension would involve loading the extension javascript and then annotating our HTML like so:

<h2>Contacts</h2>


<table class="table">
  <thead>
    <tr>
      <th>名称</th>
      <th>电子邮件</th> 
      <th></th>
    </tr>
  </thead>
  <tbody id="contacts-table" hx-get="/contacts/table" hx-ext="path-deps"  hx-trigger="path-deps" path-deps="/contacts">
    ...
  </tbody>
</table>


<h2>Add A Contact</h2>
<form hx-post="/contacts">
  <label>
    Name
        <input name="name" type="text">  
  </label>
  <label>
    Email
        <input name="email" type="email">  
  </label>
</form>

现在,当表单发布到 /contacts URL 时,path-deps 扩展将检测到该情况并在联系人表上触发 path-deps 事件,从而触发请求。

¥Now, when the form posts to the /contacts URL, the path-deps extension will detect that and trigger an path-deps event on the contacts table, therefore triggering a request.

这里的优点是你不需要对响应标头进行任何花哨的操作。缺点是即使未成功创建联系人,也会在每个 POST 上发出请求。

¥The advantage here is that you don’t need to do anything fancy with response headers. The downside is that a request will be issued on every POST, even if a contact was not successfully created.

我应该使用哪一个?

¥Which should I use?

一般来说,我建议采用第一种方法,扩大目标,特别是当需要更新的元素在 DOM 中彼此相当接近时。它简单可靠。

¥Generally I would recommend the first approach, expanding your target, especially if the elements that need to be updated are reasonably close to one another in the DOM. It is simple and reliable.

之后,我会说这是自定义事件和 OOB 交换方法之间的博弈。我倾向于自定义事件方法,因为我喜欢面向事件的系统,但这是个人喜好。你选择哪一个应该由你自己的软件工程品味决定,并且两者中的哪一个更符合你选择的服务器端技术。

¥After that, I would say it is a tossup between the custom event and an OOB swap approaches. I would lean towards the custom event approach because I like event-oriented systems, but that’s a personal preference. Which one you choose should be dictated by your own software engineering tastes and which of the two matches up better with your server side technology of choice.

最后,path-deps 方法很有趣,如果它与你的思维模型和整体系统架构很契合,它可以成为一种避免显式刷新的有趣方式。不过,除非这个概念真的吸引你,否则我会最后再看它。

¥Finally, the path-deps approach is interesting, and if it fits well with your mental model and overall system architecture, it can be a fun way to avoid explicit refreshing. I would look at it last, however, unless the concept really grabs you.