偷偷摘套内射激情视频,久久精品99国产国产精,中文字幕无线乱码人妻,中文在线中文a,性爽19p

使用 Node.js 和 htmx 構(gòu)建全棧 CRUD 應(yīng)用程序

開發(fā) 前端
在本文中,我們使用 Node 和 Express 作為后端,使用 htmx 作為前端,制作了一個(gè)全棧 CRUD 應(yīng)用程序。在此過程中,我演示了 htmx 如何簡化向 Web 應(yīng)用程序添加動(dòng)態(tài)行為,減少對(duì)復(fù)雜 JavaScript 和整頁重新加載的需求,從而使用戶體驗(yàn)更流暢、更具交互性。

htmx 是一個(gè)現(xiàn)代 JavaScript 庫,旨在通過實(shí)現(xiàn)部分 HTML 更新來增強(qiáng) Web 應(yīng)用,而無需重新加載整個(gè)頁面。與傳統(tǒng)前端框架中的 JSON 有效載荷不同,它通過有線方式發(fā)送 HTML 來實(shí)現(xiàn)這一功能。

我們將要構(gòu)建什么

我們將開發(fā)一個(gè)簡單的聯(lián)系人管理器,能夠執(zhí)行所有 CRUD 操作:創(chuàng)建、讀取、更新和刪除聯(lián)系人。通過利用 htmx,該應(yīng)用程序?qū)⑻峁﹩雾搼?yīng)用程序 (SPA) 的感覺,從而增強(qiáng)交互性和用戶體驗(yàn)。

如果用戶禁用 JavaScript,應(yīng)用程序?qū)⒁哉撍⑿碌姆绞竭\(yùn)行,從而保持可用性和可發(fā)現(xiàn)性。這種方法展示了 htmx 創(chuàng)建現(xiàn)代 Web 應(yīng)用程序的能力,同時(shí)保持它們的可訪問性和 SEO 友好性。

這就是我們最終得到的結(jié)果。

圖片圖片

本文的代碼可以在隨附的 GitHub 存儲(chǔ)庫[1]中找到。

先決條件

要學(xué)習(xí)本教程,您需要在 PC 上安裝 Node.js。如果您尚未安裝 Node,請(qǐng)前往官方 Node 下載頁面并獲取適合您系統(tǒng)的正確二進(jìn)制文件。或者,您可能想使用版本管理器安裝 Node。這種方法允許您安裝多個(gè) Node 版本并在它們之間隨意切換。

除此之外,熟悉 Node、Pug(我們將使用它們作為模板引擎)和 htmx 會(huì)有所幫助,但不是必需的。如果您想復(fù)習(xí)以上任何內(nèi)容,請(qǐng)查看我們的教程:使用 Node 構(gòu)建簡單的初學(xué)者應(yīng)用程序[2]、Pug HTML 模板預(yù)處理器指南[3]和 htmx 簡介[4]。

在開始之前,請(qǐng)運(yùn)行以下命令:

node -v
npm -v

您應(yīng)該看到如下輸出:

v20.11.1
10.4.0

這確認(rèn)了 Node 和 npm 已安裝在您的計(jì)算機(jī)上,并且可以從命令行環(huán)境進(jìn)行訪問。

設(shè)置項(xiàng)目

讓我們從搭建一個(gè)新的 Node 項(xiàng)目開始:

mkdir contact-manager
cd contact-manager
npm init -y

這應(yīng)該在項(xiàng)目根目錄中創(chuàng)建一個(gè) package.json 文件。

接下來,讓我們安裝我們需要的依賴項(xiàng):

npm i express method-override pug

在這些包中,Express 是我們應(yīng)用程序的支柱。它是一個(gè)快速且簡約的 Web 框架,提供了一種簡單的方法來處理請(qǐng)求和響應(yīng),并將 URL 路由到特定的處理函數(shù)。 Pug 將充當(dāng)我們的模板引擎,而我們將使用方法覆蓋在客戶端不支持的地方使用 HTTP 動(dòng)詞,例如 PUT 和 DELETE。

接下來,在根目錄中創(chuàng)建一個(gè) app.js 文件:

touch app.js

并添加以下內(nèi)容:

const express = require('express');
const path = require('path');
const routes = require('./routes/index');

const app = express();

app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'pug');

app.use(express.static('public'));
app.use('/', routes);

const server = app.listen(3000, () => {
  console.log(`Express is running on port ${server.address().port}`);
});

在這里,我們正在設(shè)置 Express 應(yīng)用程序的結(jié)構(gòu)。這包括將 Pug 配置為渲染視圖的視圖引擎、定義靜態(tài)資產(chǎn)的目錄以及連接路由器。

該應(yīng)用程序偵聽端口 3000,并使用控制臺(tái)日志來確認(rèn) Express 正在運(yùn)行并準(zhǔn)備好處理指定端口上的請(qǐng)求。此設(shè)置構(gòu)成了我們應(yīng)用程序的基礎(chǔ),并準(zhǔn)備好通過更多功能和路由進(jìn)行擴(kuò)展。

接下來,讓我們創(chuàng)建路由文件:

mkdir routes
touch routes/index.js

打開該文件并添加以下內(nèi)容:

const express = require('express');
const router = express.Router();

// GET /contacts
router.get('/contacts', async (req, res) => {
  res.send('It works!');
});

在這里,我們在新創(chuàng)建的路由目錄中設(shè)置基本路由。此路由在 /contacts 端點(diǎn)偵聽 GET 請(qǐng)求,并使用簡單的確認(rèn)消息進(jìn)行響應(yīng),表明一切正常。

接下來,使用以下內(nèi)容更新 package.json 文件的“scripts”部分:

"scripts": {
  "dev": "node --watch app.js"
},

這利用了 Node.js 中的新監(jiān)視模式,只要檢測到任何更改,該模式就會(huì)重新啟動(dòng)我們的應(yīng)用程序。

最后,使用 npm run dev 啟動(dòng)所有內(nèi)容,然后在瀏覽器中訪問 http://localhost:3000/contacts/。您應(yīng)該會(huì)看到一條消息“It works!”。

圖片圖片

激動(dòng)人心的時(shí)刻!

顯示所有聯(lián)系人

現(xiàn)在讓我們添加一些要顯示的聯(lián)系人。由于我們專注于 htmx,因此為了簡單起見,我們將使用硬編碼數(shù)組。這將使事情變得精簡,使我們能夠?qū)W⒂?htmx 的動(dòng)態(tài)功能,而無需復(fù)雜的數(shù)據(jù)庫集成。

對(duì)于那些有興趣稍后添加數(shù)據(jù)庫的人來說,SQLite 和 Sequelize 是不錯(cuò)的選擇,它們提供了不需要單獨(dú)數(shù)據(jù)庫服務(wù)器的基于文件的系統(tǒng)。

話雖如此,請(qǐng)將以下內(nèi)容添加到第一個(gè)路由之前的 index.js 中:

const contacts = [
  { id: 1, name: 'John Doe', email: 'john.doe@example.com' },
  { id: 2, name: 'Jane Smith', email: 'jane.smith@example.com' },
  { id: 3, name: 'Emily Johnson', email: 'emily.johnson@example.com' },
  { id: 4, name: 'Aarav Patel', email: 'aarav.patel@example.com' },
  { id: 5, name: 'Liu Wei', email: 'liu.wei@example.com' },
  { id: 6, name: 'Fatima Zahra', email: 'fatima.zahra@example.com' },
  { id: 7, name: 'Carlos Hernández', email: 'carlos.hernandez@example.com' },
  { id: 8, name: 'Olivia Kim', email: 'olivia.kim@example.com' },
  { id: 9, name: 'Kwame Nkrumah', email: 'kwame.nkrumah@example.com' },
  { id: 10, name: 'Chen Yu', email: 'chen.yu@example.com' },
];

現(xiàn)在,我們需要為路由創(chuàng)建一個(gè)顯示模板。創(chuàng)建一個(gè)包含 index.pug 文件的 views 文件夾:

mkdir views
touch views/index.pug

并添加以下內(nèi)容:

doctype html
html
  head
    meta(charset='UTF-8')
    title Contact Manager

    link(rel='preconnect', )
    link(rel='preconnect', , crossorigin)
    link(, rel='stylesheet')

    link(rel='stylesheet', href='/styles.css')
  body
    header
      a(href='/contacts')
        h1 Contact Manager

    section#sidebar
      ul.contact-list
        each contact in contacts
          li #{contact.name}
      div.actions
        a(href='/contacts/new') New Contact

    main#content
      p Select a contact

    script(src='https://unpkg.com/htmx.org@1.9.10')

在此模板中,我們?yōu)閼?yīng)用程序布置 HTML 結(jié)構(gòu)。在 head 部分,我們包含了來自 Google Fonts 的 Roboto 字體和自定義樣式的樣式表。

正文分為標(biāo)題、用于列出聯(lián)系人的側(cè)邊欄以及用于存放所有聯(lián)系信息的主要內(nèi)容區(qū)域。內(nèi)容區(qū)域當(dāng)前包含一個(gè)占位符。在正文的末尾,我們還包含來自 CDN 的最新版本的 htmx 庫。

該模板期望接收一個(gè)聯(lián)系人數(shù)組(在 contacts 變量中),我們在側(cè)邊欄中對(duì)其進(jìn)行迭代,并使用 Pug 的插值語法在無序列表中輸出每個(gè)聯(lián)系人姓名。

接下來,讓我們創(chuàng)建自定義樣式表:

mkdir public
touch public/styles.css

我不想在這里列出樣式。請(qǐng)從隨附的 GitHub 存儲(chǔ)庫中的 CSS 文件[5]中復(fù)制它們,或者隨意添加一些您自己的 CSS 文件。 ??

回到 index.js,更新路由以使用模板:

// GET /contacts
router.get('/contacts', (req, res) => {
  res.render('index', { contacts });
});

現(xiàn)在,當(dāng)您刷新頁面時(shí),您應(yīng)該會(huì)看到類似這樣的內(nèi)容。

圖片圖片

顯示單個(gè)聯(lián)系人

到目前為止,我們所做的只是建立了一個(gè)基本的 Express 應(yīng)用程序。讓我們改變一下,最后添加 htmx。下一步要做的是,當(dāng)用戶點(diǎn)擊側(cè)邊欄中的聯(lián)系人時(shí),該聯(lián)系人的信息就會(huì)顯示在主內(nèi)容區(qū)域--自然不需要重新載入整個(gè)頁面。

首先,讓我們將側(cè)邊欄移至其自己的模板中:

touch views/sidebar.pug

將以下內(nèi)容添加到這個(gè)新文件中:

ul.contact-list
  each contact in contacts
    li
      a(
        href=`/contacts/${contact.id}`,
        hx-get=`/contacts/${contact.id}`,
        hx-target='#content',
        hx-push-url='true'
      )= contact.name

div.actions
  a(href='/contacts/new') New Contact

這里我們?yōu)槊總€(gè)聯(lián)系人創(chuàng)建了一個(gè)指向 /contacts/${contact.id} 的鏈接,并添加了三個(gè) htmx 屬性:

  • hx-get:當(dāng)用戶單擊鏈接時(shí),htmx 將攔截單擊并通過 Ajax 向 /contacts/${contact.id} 端點(diǎn)發(fā)出 GET 請(qǐng)求。
  • hx-target:當(dāng)請(qǐng)求完成時(shí),響應(yīng)將被插入到 ID 為 content 的 div 中。我們在這里沒有指定任何類型的交換策略,因此 div 的內(nèi)容將被 Ajax 請(qǐng)求返回的內(nèi)容替換。這是默認(rèn)行為。
  • hx-push-url:這將確保 htx-get 中指定的值被推送到瀏覽器的歷史堆棧中,從而更改 URL。

更新 index.pug 以使用我們的模板:

section#sidebar
  include sidebar.pug

請(qǐng)記?。篜ug 對(duì)空格敏感,因此請(qǐng)務(wù)必使用正確的縮進(jìn)。

現(xiàn)在讓我們在 index.js 中創(chuàng)建一個(gè)新端點(diǎn)以返回 htmx 期望的 HTML 響應(yīng):

// GET /contacts/1
router.get('/contacts/:id', (req, res) => {
  const { id } = req.params;
  const contact = contacts.find((c) => c.id === Number(id));

  res.send(`
    <h2>${contact.name}</h2>
    <p><strong>Name:</strong> ${contact.name}</p>
    <p><strong>Email:</strong> ${contact.email}</p>
  `);
});

如果保存并刷新瀏覽器,您現(xiàn)在應(yīng)該能夠查看每個(gè)聯(lián)系人的詳細(xì)信息。

圖片圖片

網(wǎng)絡(luò)上的 HTML

讓我們花點(diǎn)時(shí)間了解一下這里發(fā)生了什么。正如文章開頭提到的,htmx 通過網(wǎng)絡(luò)傳輸 HTML,而不是傳統(tǒng)前端框架的 JSON 有效負(fù)載。

如果我們打開瀏覽器的開發(fā)人員工具,切換到“Network”選項(xiàng)卡并單擊其中一個(gè)聯(lián)系人,我們就可以看到這一點(diǎn)。收到來自前端的請(qǐng)求后,我們的 Express 應(yīng)用程序會(huì)生成顯示該聯(lián)系人所需的 HTML,并將其發(fā)送到瀏覽器,其中 htmx 將其交換到 UI 中的正確位置。

圖片圖片

處理全頁刷新

所以事情進(jìn)展得很順利,是吧?感謝 htmx,我們通過在錨標(biāo)記上指定幾個(gè)屬性來使頁面動(dòng)態(tài)化。不幸的是,有一個(gè)問題……

如果您顯示聯(lián)系人,然后刷新頁面,我們可愛的用戶界面就會(huì)消失,您看到的只是裸露的聯(lián)系方式詳細(xì)信息。如果您直接在瀏覽器中加載 URL,也會(huì)發(fā)生同樣的情況。

如果你仔細(xì)想想,其原因是顯而易見的。當(dāng)您訪問 http://localhost:3000/contacts/1 之類的 URL 時(shí), '/contacts/:id' 的 Express 路由將啟動(dòng)并返回聯(lián)系人的 HTML,正如我們告訴它的那樣。它對(duì)我們用戶界面的其余部分一無所知。

為了解決這個(gè)問題,我們需要做一些改動(dòng)。在服務(wù)器上,我們需要檢查是否存在 HX-Request 標(biāo)頭,它表明請(qǐng)求來自 htmx。如果存在,我們就可以發(fā)送部分內(nèi)容。否則,我們需要發(fā)送整個(gè)頁面。

像這樣更改路由處理程序:

// GET /contacts/1
router.get('/contacts/:id', (req, res) => {
  const { id } = req.params;
  const contact = contacts.find((c) => c.id === Number(id));

  if (req.headers['hx-request']) {
    res.send(`
      <h2>${contact.name}</h2>
      <p><strong>Name:</strong> ${contact.name}</p>
      <p><strong>Email:</strong> ${contact.email}</p>
    `);
  } else {
    res.render('index', { contacts });
  }
});

現(xiàn)在,當(dāng)您重新加載頁面時(shí),用戶界面不會(huì)消失。但是,它確實(shí)會(huì)從您正在查看的任何聯(lián)系人恢復(fù)為消息“選擇聯(lián)系人”,這并不理想。

為了解決這個(gè)問題,我們可以在 index.pug 模板中引入 case 語句:

main#content
  case action
    when 'show'
      h2 #{contact.name}
      p #[strong Name:] #{contact.name}
      p #[strong Email:] #{contact.email}
    when 'new'
      // Coming soon
    when 'edit'
      // Coming soon
    default
      p Select a contact

最后更新路由處理程序:

if (req.headers['hx-request']) {
  // As before
} else {
  res.render('index', { action: 'show', contacts, contact });
}

請(qǐng)注意,我們現(xiàn)在傳入一個(gè) contact 變量,該變量將在整個(gè)頁面重新加載時(shí)使用。

這樣,我們的應(yīng)用程序應(yīng)該能夠承受刷新或直接加載聯(lián)系人。

快速重構(gòu)

雖然這樣做可行,但您可能會(huì)注意到,我們的路由處理程序和主 pug 模板中都有一些重復(fù)的內(nèi)容。這種情況并不理想,只要聯(lián)系人的屬性不超過幾個(gè),或者我們需要使用一些邏輯來決定顯示哪些屬性,事情就會(huì)開始變得臃腫。

為了解決這個(gè)問題,讓我們將聯(lián)系人移動(dòng)到自己的模板中:

touch views/contact.pug

在新創(chuàng)建的模板中,添加以下內(nèi)容:

h2 #{contact.name}

p #[strong Name:] #{contact.name}
p #[strong Email:] #{contact.email}

在主模板( index.pug )中:

main#content
  case action
    when 'show'
      include contact.pug

還有我們的路由處理程序:

if (req.headers['hx-request']) {
  res.render('contact', { contact });
} else {
  res.render('index', { action: 'show', contacts, contact });
}

事情應(yīng)該仍然像以前一樣工作,但現(xiàn)在我們已經(jīng)刪除了重復(fù)的代碼。

新的聯(lián)系表

我們要關(guān)注的下一個(gè)任務(wù)是創(chuàng)建新聯(lián)系人。本教程的這一部分將指導(dǎo)您設(shè)置表單和后端邏輯,使用 htmx 動(dòng)態(tài)處理提交。

讓我們從更新側(cè)邊欄模板開始。更改:

div.actions
  a(href='/contacts/new') New Contact

… 到:

div.actions
  a(
    href='/contacts/new',
    hx-get='/contacts/new',
    hx-target='#content',
    hx-push-url='true'
  ) New Contact

這將使用與鏈接相同的 htmx 屬性來顯示聯(lián)系人:hx-get 將通過 Ajax 向 /contacts/new 端點(diǎn)發(fā)出 GET 請(qǐng)求,hx-target 將指定插入響應(yīng)的位置,hx-push-url 將確保更改 URL。

現(xiàn)在讓我們?yōu)楸韱蝿?chuàng)建一個(gè)新模板:

touch views/form.pug

并添加以下代碼:

h2 New Contact

form(
  actinotallow='/contacts',
  method='POST',
  hx-post='/contacts',
  hx-target='#sidebar',
  hx-on::after-request='if(event.detail.successful) this.reset()'
)
  label(for='name') Name:
  input#name(type='text', name='name', required)

  label(for='email') Email:
  input#email(type='email', name='email', required)

  div.actions
    button(type='submit') Submit

在這里,我們使用 hx-post 屬性告訴 htmx 攔截表單提交,并向 /contacts 端點(diǎn)發(fā)出帶有表單數(shù)據(jù)的 POST 請(qǐng)求。結(jié)果(更新的聯(lián)系人列表)將被插入到側(cè)邊欄中。在這種情況下,我們不想更改 URL,因?yàn)橛脩艨赡芟胍斎攵鄠€(gè)新聯(lián)系人。但是,我們確實(shí)希望在成功提交后清空表單,這就是 hx-on::after-request 的作用。 hx-on* 屬性允許您內(nèi)聯(lián)嵌入腳本以直接響應(yīng)元素上的事件。你可以在這里讀更多[6]關(guān)于它的內(nèi)容。

接下來,我們在 index.js 中添加表單的路由:

// GET /contacts
...

// GET /contacts/new
router.get('/contacts/new', (req, res) => {
  if (req.headers['hx-request']) {
    res.render('form');
  } else {
    res.render('index', { action: 'new', contacts, contact: {} });
  }
});

// GET /contacts/1
...

路由順序在這里很重要。如果您先有 '/contacts/:id' 路由,那么 Express 將嘗試查找 ID 為 new 的聯(lián)系人。

最后,更新我們的 index.pug 模板以使用以下形式:

when 'new'
  include form.pug

刷新頁面,此時(shí)您應(yīng)該能夠通過單擊側(cè)欄中的“New Contact”鏈接來呈現(xiàn)新的聯(lián)系人表單。

圖片圖片

創(chuàng)建聯(lián)系人

現(xiàn)在我們需要?jiǎng)?chuàng)建一個(gè)路由來處理表單提交。

首先更新 app.js 以使我們能夠訪問路由處理程序中的表單數(shù)據(jù)。

const express = require('express');
const path = require('path');
const routes = require('./routes/index');

const app = express();

app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'pug');

+ app.use(express.urlencoded({ extended: true }));
app.use(express.static('public'));
app.use('/', routes);

const server = app.listen(3000, () => {
  console.log(`Express is running on port ${server.address().port}`);
});

以前,我們會(huì)使用 body-parser 包,但我最近了解到這不再是必要的。

然后將以下內(nèi)容添加到 index.js :

// POST /contacts
router.post('/contacts', (req, res) => {
  const newContact = {
    id: contacts.length + 1,
    name: req.body.name,
    email: req.body.email,
  };

  contacts.push(newContact);

  if (req.headers['hx-request']) {
    res.render('sidebar', { contacts });
  } else {
    res.render('index', { action: 'new', contacts, contact: {} });
  }
});

在這里,我們使用從客戶端收到的數(shù)據(jù)創(chuàng)建一個(gè)新聯(lián)系人,并將其添加到 contacts 數(shù)組中。然后,我們重新渲染側(cè)邊欄,并向其傳遞更新的聯(lián)系人列表。

請(qǐng)注意,如果您正在制作任何類型的有用戶的應(yīng)用程序,則由您負(fù)責(zé)驗(yàn)證從客戶端接收的數(shù)據(jù)。在我們的示例中,我添加了一些基本的客戶端驗(yàn)證,但這很容易被繞過。

我上面鏈接的 Node 教程中有一個(gè)示例,說明如何使用 express-validator[7] 包來驗(yàn)證服務(wù)器上的輸入。

現(xiàn)在,如果您刷新瀏覽器并嘗試添加聯(lián)系人,它應(yīng)該按預(yù)期工作:新聯(lián)系人應(yīng)添加到側(cè)邊欄,并且應(yīng)重置表單。

添加 toast 消息提示

這很好,但現(xiàn)在我們需要一種方法來通知用戶聯(lián)系人已添加。在典型的應(yīng)用程序中,我們會(huì)使用 toast 消息——一種臨時(shí)通知,提醒用戶操作的結(jié)果。

我們使用 htmx 遇到的問題是,我們在成功創(chuàng)建新聯(lián)系人后更新側(cè)邊欄,但這不是我們希望顯示 toast 消息的位置。更好的位置將位于新聯(lián)系表格上方。

為了解決這個(gè)問題,我們可以使用 hx-swap-oob 屬性。這允許您指定響應(yīng)中的某些內(nèi)容應(yīng)交換到目標(biāo)以外的 DOM 中,即“Out of Band”。

更新路由處理程序如下:

if (req.headers['hx-request']) {
  res.render('sidebar', { contacts }, (err, sidebarHtml) => {
    const html = `
      <main id="content" hx-swap-oob="afterbegin">
        <p class="flash">Contact was successfully added!</p>
      </main>
      ${sidebarHtml}
    `;
    res.send(html);
  });
} else {
  res.render('index', { action: 'new', contacts, contact: {} });
}

在這里,我們像以前一樣渲染側(cè)邊欄,但向 render 方法傳遞一個(gè)匿名函數(shù)作為第三個(gè)參數(shù)。該函數(shù)接收通過調(diào)用 res.render('sidebar', { contacts }) 生成的 HTML,然后我們可以使用它來組裝最終響應(yīng)。

通過指定交換策略 "afterbegin" ,將 toast 消息插入到容器的頂部。

現(xiàn)在,當(dāng)我們添加聯(lián)系人時(shí),我們應(yīng)該會(huì)收到一條不錯(cuò)的消息,告訴我們發(fā)生了什么。

圖片圖片

編輯聯(lián)系人

為了更新聯(lián)系人,我們將重用上一節(jié)中創(chuàng)建的表單。

讓我們首先更新 contact.pug 模板以添加以下內(nèi)容:

div.actions
  a(
    href=`/contacts/${contact.id}/edit`,
    hx-get=`/contacts/${contact.id}/edit`,
    hx-target='#content',
    hx-push-url='true'
  ) Edit Contact

這將在聯(lián)系人詳細(xì)信息下方添加一個(gè)編輯聯(lián)系人按鈕。正如我們之前所見,當(dāng)單擊鏈接時(shí), hx-get 將通過 Ajax 向 /${contact.id}/edit 端點(diǎn)發(fā)出 GET 請(qǐng)求, hx-target 將指定插入位置響應(yīng), hx-push-url 將確保 URL 發(fā)生更改。

現(xiàn)在讓我們更改 index.pug 模板以使用以下形式:

when 'edit'
  include form.pug

還添加一個(gè)路由處理程序來顯示表單:

// GET /contacts/1/edit
router.get('/contacts/:id/edit', (req, res) => {
  const { id } = req.params;
  const contact = contacts.find((c) => c.id === Number(id));

  if (req.headers['hx-request']) {
    res.render('form', { contact });
  } else {
    res.render('index', { action: 'edit', contacts, contact });
  }
});

請(qǐng)注意,我們使用請(qǐng)求中的 ID 檢索聯(lián)系人,然后將該聯(lián)系人傳遞到表單。

我們還需要更新新的聯(lián)系人處理程序以執(zhí)行相同的操作,但此處傳遞一個(gè)空對(duì)象:

// GET /contacts/new
router.get('/contacts/new', (req, res) => {
  if (req.headers['hx-request']) {
-    res.render('form');
+    res.render('form', { contact: {} });
  } else {
    res.render('index', { action: 'new', contacts, contact: {} });
  }
});

然后我們需要更新表單本身:

- isEditing = () => !(Object.keys(contact).length === 0);

h2=isEditing() ? "Edit Contact" : "New Contact"

form(
  actinotallow=isEditing() ? `/update/${contact.id}?_method=PUT` : '/contacts',
  method='POST',

  hx-post=isEditing() ? false : '/contacts',
  hx-put=isEditing() ? `/update/${contact.id}` : false,
  hx-target='#sidebar',
  hx-push-url=isEditing() ? `/contacts/${contact.id}` : false
  hx-on::after-request='if(event.detail.successful) this.reset()',
)
  label(for='name') Name:
  input#name(type='text', name='name', required, value=contact.name)

  label(for='email') Email:
  input#email(type='email', name='email', required, value=contact.email)

  div.actions
    button(type='submit') Submit

當(dāng)我們向此表單傳遞聯(lián)系人或空對(duì)象時(shí),我們現(xiàn)在有一種簡單的方法來確定我們是否處于“編輯”或“創(chuàng)建”模式。我們可以通過檢查 Object.keys(contact).length 來做到這一點(diǎn)。我們還可以使用 Pug 的無緩沖代碼語法將此檢查提取到文件頂部的一個(gè)小輔助函數(shù)中。

一旦我們知道自己所處的模式,我們就可以有條件地更改頁面標(biāo)題,然后決定向表單標(biāo)記添加哪些屬性。對(duì)于編輯表單,我們需要添加 hx-put 屬性并將其設(shè)置為 /update/${contact.id} 。保存聯(lián)系人詳細(xì)信息后,我們還需要更新 URL。

為了做到這一切,我們可以利用這樣一個(gè)事實(shí):如果條件返回 false ,Pug 將從標(biāo)簽中省略該屬性。

這意味著:

form(
  actinotallow=isEditing() ? `/update/${contact.id}?_method=PUT` : '/contacts',
  method='POST',

  hx-post=isEditing() ? false : '/contacts',
  hx-put=isEditing() ? `/update/${contact.id}` : false,
  hx-target='#sidebar',
  hx-on::after-request='if(event.detail.successful) this.reset()',
  hx-push-url=isEditing() ? `/contacts/${contact.id}` : false
)

當(dāng) isEditing() 返回 false 時(shí)將編譯為以下內(nèi)容:

<form
  action="/contacts"
  method="POST"
  hx-post="/contacts"
  hx-target="#sidebar"
  hx-on::after-request="if(event.detail.successful) this.reset()"
>
  ...
</form>

但是當(dāng) isEditing() 返回 true 時(shí),它將編譯為:

<form
  action="/update/1?_method=PUT"
  method="POST"
  hx-put="/update/1"
  hx-target="#sidebar"
  hx-on::after-request="if(event.detail.successful) this.reset()"
  hx-push-url="/contacts/1"
>
  ...
</form>

在其更新狀態(tài)下,請(qǐng)注意表單操作是 "/update/1?_method=PUT" 。添加此查詢字符串參數(shù)是因?yàn)槲覀冋谑褂梅椒ǜ采w包,它將使我們的路由器響應(yīng) PUT 請(qǐng)求。

開箱即用的 htmx 可以發(fā)送 PUT 和 DELETE 請(qǐng)求,但瀏覽器卻不行。這意味著,如果我們要處理 JavaScript 被禁用的情況,就需要復(fù)制我們的路由處理程序,讓它同時(shí)響應(yīng) PUT(htmx)和 POST(瀏覽器)。使用這種中間件將使我們的代碼保持 DRY。

讓我們繼續(xù)將其添加到 app.js :

const express = require('express');
const path = require('path');
+ const methodOverride = require('method-override');
const routes = require('./routes/index');

const app = express();

app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'pug');

+ app.use(methodOverride('_method'));
app.use(express.urlencoded({ extended: true }));
app.use(express.static('public'));
app.use('/', routes);

const server = app.listen(3000, () => {
  console.log(`Express is running on port ${server.address().port}`);
});

最后,讓我們用新的路由處理程序更新 index.js :

// PUT /contacts/1
router.put('/update/:id', (req, res) => {
  const { id } = req.params;

  const newContact = {
    id: Number(id),
    name: req.body.name,
    email: req.body.email,
  };

  const index = contacts.findIndex((c) => c.id === Number(id));

  if (index !== -1) contacts[index] = newContact;

  if (req.headers['hx-request']) {
    res.render('sidebar', { contacts }, (err, sidebarHtml) => {
      res.render('contact', { contact: contacts[index] }, (err, contactHTML) => {
        const html = `
          ${sidebarHtml}
          <main id="content" hx-swap-oob="true">
            <p class="flash">Contact was successfully updated!</p>
            ${contactHTML}
          </main>
        `;

        res.send(html);
      });
    });
  } else {
    res.redirect(`/contacts/${index + 1}`);
  }
});

希望現(xiàn)在沒有什么太神秘的事情了。在處理程序的開頭,我們從請(qǐng)求參數(shù)中獲取聯(lián)系人 ID。然后,我們找到想要更新的聯(lián)系人,并將其替換為根據(jù)我們收到的表單數(shù)據(jù)創(chuàng)建的新聯(lián)系人。

在處理 htmx 請(qǐng)求時(shí),我們首先用更新的聯(lián)系人列表呈現(xiàn)側(cè)邊欄模板。然后,我們用更新的聯(lián)系人渲染聯(lián)系人模板,并使用這兩次調(diào)用的結(jié)果來組合我們的響應(yīng)。與之前一樣,我們使用 “Out of Band” 更新創(chuàng)建一條 toast 消息,告知用戶聯(lián)系人已更新。

此時(shí),您應(yīng)該能夠更新聯(lián)系人。

刪除聯(lián)系人

最后一個(gè)難題是刪除聯(lián)系人的能力。讓我們在聯(lián)系人模板中添加一個(gè)按鈕來執(zhí)行此操作:

div.actions
  form(method='POST', actinotallow=`/delete/${contact.id}?_method=DELETE`)
    button(
      type='submit',
      hx-delete=`/delete/${contact.id}`,
      hx-target='#sidebar',
      hx-push-url='/contacts'
      class='link'
    ) Delete Contact

  a(
    // as before
  )

請(qǐng)注意,最好使用表單和按鈕來發(fā)出 DELETE 請(qǐng)求。表單是為導(dǎo)致更改(例如刪除)的操作而設(shè)計(jì)的,這確保了語義的正確性。此外,使用鏈接進(jìn)行刪除操作可能存在風(fēng)險(xiǎn),因?yàn)樗阉饕婵赡軙?huì)無意中跟蹤鏈接,從而可能導(dǎo)致不必要的刪除。

話雖這么說,我添加了一些 CSS 來將按鈕設(shè)置為鏈接樣式,因?yàn)榘粹o很難看。如果您之前從存儲(chǔ)庫復(fù)制了樣式,那么您的代碼中已經(jīng)包含了該樣式。

最后,我們的路由處理程序在 index.js 中:

// DELETE /contacts/1
router.delete('/delete/:id', (req, res) => {
  const { id } = req.params;
  const index = contacts.findIndex((c) => c.id === Number(id));

  if (index !== -1) contacts.splice(index, 1);
  if (req.headers['hx-request']) {
    res.render('sidebar', { contacts }, (err, sidebarHtml) => {
      const html = `
        <main id="content" hx-swap-oob="true">
          <p class="flash">Contact was successfully deleted!</p>
        </main>
        ${sidebarHtml}
      `;
      res.send(html);
    });
  } else {
    res.redirect('/contacts');
  }
});

刪除聯(lián)系人后,我們將更新側(cè)邊欄并向用戶顯示一條提示消息。

圖片圖片

更進(jìn)一步

就這樣吧。

在本文中,我們使用 Node 和 Express 作為后端,使用 htmx 作為前端,制作了一個(gè)全棧 CRUD 應(yīng)用程序。在此過程中,我演示了 htmx 如何簡化向 Web 應(yīng)用程序添加動(dòng)態(tài)行為,減少對(duì)復(fù)雜 JavaScript 和整頁重新加載的需求,從而使用戶體驗(yàn)更流暢、更具交互性。

作為額外的好處,該應(yīng)用程序無需 JavaScript 也能正常運(yùn)行。

然而,雖然我們的應(yīng)用程序功能齊全,但不可否認(rèn)它還有些簡陋。如果您希望繼續(xù)探索 htmx,您可能希望考慮在應(yīng)用程序狀態(tài)之間實(shí)現(xiàn)視圖轉(zhuǎn)換,或者向表單添加一些進(jìn)一步的驗(yàn)證 - 例如,驗(yàn)證電子郵件地址是否來自特定域。

我在 htmx 簡介[8]中提供了這兩件事(以及更多)的示例。

https://www.sitepoint.com/node-js-htmx-build-full-stack-app

責(zé)任編輯:武曉燕 來源: 獨(dú)立開發(fā)者張張
相關(guān)推薦

2022-05-09 17:33:23

PWA漸進(jìn)式Web應(yīng)用程序離線優(yōu)先

2024-03-27 11:18:02

2013-03-28 14:54:36

2021-06-15 15:03:21

MongoDBNode.jsCRUD

2020-09-04 15:06:04

Docker容器化Node.js

2013-05-17 09:41:02

Node.js云應(yīng)用開發(fā)IaaS

2020-09-22 07:35:42

Node.jsVue.js文件壓縮

2023-01-10 14:11:26

2024-12-24 08:12:59

2022-09-12 16:02:32

Docker安全Node.js

2023-03-07 14:31:44

Node.jsPython應(yīng)用程序

2023-04-18 15:18:10

2022-08-22 07:26:32

Node.js微服務(wù)架構(gòu)

2020-01-15 14:20:07

Node.js應(yīng)用程序javascript

2022-12-14 14:40:27

Node.js開發(fā)應(yīng)用程序

2022-09-12 15:58:50

node.js微服務(wù)Web

2012-09-29 11:13:15

Node.JS前端開發(fā)Node.js打包

2015-11-20 17:09:36

jsWeb應(yīng)用程序

2012-09-17 11:26:14

IBMdw

2023-10-26 01:28:02

點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)