<?xml version="1.0" encoding="UTF-8"?>
<rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/"
    xmlns:atom="http://www.w3.org/2005/Atom" xmlns:media="http://search.yahoo.com/mrss/" version="2.0">
    <channel>
        
        <title>
            <![CDATA[ 表单 - freeCodeCamp.org ]]>
        </title>
        <description>
            <![CDATA[ freeCodeCamp 是一个免费学习编程的开发者社区，涵盖 Python、HTML、CSS、React、Vue、BootStrap、JSON 教程等，还有活跃的技术论坛和丰富的社区活动，在你学习编程和找工作时为你提供建议和帮助。 ]]>
        </description>
        <link>https://www.freecodecamp.org/chinese/news/</link>
        <image>
            <url>https://cdn.freecodecamp.org/universal/favicons/favicon.png</url>
            <title>
                <![CDATA[ 表单 - freeCodeCamp.org ]]>
            </title>
            <link>https://www.freecodecamp.org/chinese/news/</link>
        </image>
        <generator>Eleventy</generator>
        <lastBuildDate>Thu, 21 May 2026 15:49:27 +0000</lastBuildDate>
        <atom:link href="https://www.freecodecamp.org/chinese/news/tag/forms/rss.xml" rel="self" type="application/rss+xml" />
        <ttl>60</ttl>
        
            <item>
                <title>
                    <![CDATA[ 如何使用 Google Sheets API 创建反馈表 ]]>
                </title>
                <description>
                    <![CDATA[ 原文：How to Create a Feedback Form using the Google Sheets API [https://www.freecodecamp.org/news/create-a-feedback-form-using-nextjs-and-google-sheets-api/] ，作者：Georgey V B [https://www.freecodecamp.org/news/author/georgey/] Google 表单提供了一种创建在线表单和收集用户数据的简单方法。在本教程中，我们将使用 Google Sheets 和 Next.js 来构建一个简单的表单。 我们将使用 Next.js 作为前端，使用 Google Sheets 作为后端，来发送我们通过表单接收到的数据。这样我们就可以学习如何使用 Next.js 和 Google Sheets 来构建一个简单的表单。 这是我们将在本教程中介绍的内容： 1. 如何在 Google Cloud Console 中创建新项目 2. 如何将新项目与 Google Sheet 连接 3. 如何在 ]]>
                </description>
                <link>https://www.freecodecamp.org/chinese/news/create-a-feedback-form-using-nextjs-and-google-sheets-api/</link>
                <guid isPermaLink="false">614daaf696c25a0641857fca</guid>
                
                    <category>
                        <![CDATA[ Google ]]>
                    </category>
                
                    <category>
                        <![CDATA[ 表单 ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Miya Liu ]]>
                </dc:creator>
                <pubDate>Thu, 28 Apr 2022 09:20:00 +0000</pubDate>
                <media:content url="https://chinese.freecodecamp.org/news/content/images/2021/09/Build-a-feedback-form-using-Google-Sheets-API-3.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>原文：<a href="https://www.freecodecamp.org/news/create-a-feedback-form-using-nextjs-and-google-sheets-api/">How to Create a Feedback Form using the Google Sheets API</a>，作者：<a href="https://www.freecodecamp.org/news/author/georgey/">Georgey V B</a></p><p>Google 表单提供了一种创建在线表单和收集用户数据的简单方法。在本教程中，我们将使用 Google Sheets 和 Next.js 来构建一个简单的表单。</p><p>我们将使用 Next.js 作为前端，使用 Google Sheets 作为后端，来发送我们通过表单接收到的数据。这样我们就可以学习如何使用 Next.js 和 Google Sheets 来构建一个简单的表单。</p><p>这是我们将在本教程中介绍的内容：<br>1. 如何在 Google Cloud Console 中创建新项目<br>2. 如何将新项目与 Google Sheet 连接<br>3. 如何在 Next.js 应用程序中创建前端表单<br>4. 如何将表单连接到 Google Sheet</p><blockquote>为了帮助你理解，我创建了一个 <a href="https://github.com/GeoBrodas/nextjs-form-using-google-sheets-api">GitHub 仓库</a>，如果你有不清楚的地方，可以参考。</blockquote><h1 id="-google-cloud-console-">如何在 Google Cloud Console 中创建新项目</h1><p>要访问 Google Sheets API，我们首先需要在 Google Cloud Console 上创建一个新项目。访问这个<a href="https://cloud.google.com/">站点</a>，转到控制台并创建一个新项目。</p><figure class="kg-card kg-image-card"><img src="https://www.freecodecamp.org/news/content/images/2021/09/cloud-new.png" class="kg-image" alt="cloud-new" width="600" height="400" loading="lazy"></figure><p>创建新项目后，转到 <strong><strong>APIs and Services</strong></strong>，然后单击 <strong><strong>Enable APIs and Services</strong></strong>。</p><figure class="kg-card kg-image-card"><img src="https://www.freecodecamp.org/news/content/images/2021/09/cloud-enable.png" class="kg-image" alt="cloud-enable" width="600" height="400" loading="lazy"></figure><p>从库中搜索 Google Sheets 并启用它。</p><figure class="kg-card kg-image-card"><img src="https://www.freecodecamp.org/news/content/images/2021/09/enable-sheets-api.png" class="kg-image" alt="enable-sheets-api" width="600" height="400" loading="lazy"></figure><p>现在，转到 APIs and Services，点击 <strong><strong>Credentials</strong></strong>，然后点击 <strong><strong>New Credential</strong></strong>，创建一个新的 Service 账号。</p><figure class="kg-card kg-image-card"><img src="https://www.freecodecamp.org/news/content/images/2021/09/create-cred.png" class="kg-image" alt="create-cred" width="600" height="400" loading="lazy"></figure><p>给它一个合适的名字并填写所有细节。生成服务帐户后，将电子邮件 ID 复制到某处。之后我们需要将其添加到我们的 Google 表单中。我们刚刚创建了一个 Bot 帐户来处理将从前端发送的各种请求。</p><p>接下来，单击 <strong>Credentials</strong> 中的 Service account，然后将它们移至 <strong>Keys</strong>。单击 <strong><strong>Add Key</strong></strong>，确保将其设置为 JSON 格式。</p><figure class="kg-card kg-image-card"><img src="https://www.freecodecamp.org/news/content/images/2021/09/google_key.png" class="kg-image" alt="google_key" width="600" height="400" loading="lazy"></figure><p>创建新密钥时将下载一个文件，它包含我们将前端应用程序连接到 Google 表单时所需的所有环境变量。</p><h2 id="-google-sheet">如何将新项目连接到 Google Sheet</h2><p>现在让我们将 Google Cloud Console 上新创建的项目与 Google Sheet 连接起来。访问 <a href="http://sheets.google.com/">Google Sheets</a> 并创建一个新的电子表单。</p><p>在我们继续之前，请随意放入一些原始数据，以便我们在下一节中调用请求时可以获取一些内容。</p><p>完成后，单击 Share 并添加我们刚刚创建的服务帐户电子邮件。确保你授予它编辑器访问权限 <strong><strong>Editor access</strong></strong> 并取消 <strong><strong>Notify people</strong></strong>（选中通知人员）。</p><figure class="kg-card kg-image-card"><img src="https://www.freecodecamp.org/news/content/images/2021/09/share.png" class="kg-image" alt="share" width="600" height="400" loading="lazy"></figure><p>现在是有趣的部分。让我们转到代码编辑器并为我们的表单创建前端。</p><h2 id="-">如何创建前端表单</h2><p>为了构建前端，我们将使用 Next.js，并使用 API-routes 功能将 POST 请求发送到我们的 Google Sheet。</p><p>使用以下命令安装 Next：</p><pre><code class="language-bash">npx create-next-app
</code></pre><p>为了构建表单，并加快开发过程，我们将使用一些第三方包。所以继续安装以下内容：</p><pre><code class="language-bash">npm i @chakra-ui/react @emotion/react@^11 @emotion/styled@^11 framer-motion@^4 react-hook-form
</code></pre><ul><li>Chakra-UI：一个可访问的框架，它帮助我加快了大多数应用程序的前端设计。</li><li>React-Hook-Form：帮助你即时构建具有客户端验证的高效表单。</li></ul><p>在本教程中，我将更多地关注执行表单的功能，而不是构建客户端验证。<a href="https://react-hook-form.com/get-started#Applyvalidation">这里</a>是使用 <a href="https://react-hook-form.com/">React-Hook-Form</a> 添加客户端验证的完整指南。当然也可以随意访问 <a href="https://chakra-ui.com/docs/getting-started">Chakra-UI</a> 文档。</p><p>安装所有软件包后，使用任何代码编辑器打开它。在 Next.js 中，你在 <code>/pages</code> 文件夹中创建的每个文件都是一个单独的路由。你可以创建一个新文件，但在这里我将使用根文件本身，即 <code>/pages/index.js</code>。</p><p>清除所有预先生成的代码行。现在，让我们为 Form 创建一个基本结构。</p><pre><code class="language-js">import { VStack, Text, Input } from "@chakra-ui/react"

function Home () {
    function submitHandler () {
     // POST request
    }
    
    return (
        &lt;VStack&gt;
          &lt;Text fontSize="2xl" fontWeight="bold"&gt;
            Your response matters!
          &lt;/Text&gt;
          
          &lt;form onSubmit={submitHandler}&gt;
              &lt;Input placeholder="Enter Name" /&gt;
              &lt;Button&gt;Submit!&lt;/Button&gt;
          &lt;/form&gt;
        &lt;/VStack&gt;
    )
}
</code></pre><p>VStack 包含下方所有元素。这是 <code>flex-direction: column</code> 的简写。其余的代码应该是不言自明的。</p><p>Chakra-UI 的美妙之处在于它的每个组件都非常类似于 HTML 元素，大大减少了学习曲线。</p><p>你可以添加更多你选择的输入字段。这是最终结果：</p><figure class="kg-card kg-image-card"><img src="https://www.freecodecamp.org/news/content/images/2021/09/form.png" class="kg-image" alt="form" width="600" height="400" loading="lazy"></figure><p>现在让我们处理用户提交时的表单响应。为此，我们将使用 <code>react-hook-form</code>。</p><p>为了从表单中获取响应，我们必须导入 <code>useForm</code> 钩子，如下所示：</p><pre><code class="language-js">import { useForm } from 'react-hook-form';
</code></pre><p>从钩子中，解构以下内容：</p><pre><code class="language-js">const {
    register,
    handleSubmit
  } = useForm();</code></pre><p>用 <code>handleSubmit</code> 包裹我们之前创建的 <code>submitHandler</code>：</p><pre><code class="language-js">&lt;form onSubmit={handleSubmit(submitHandler)}&gt;
   {/* Input fields here */}               
&lt;/form&gt;</code></pre><p>现在将 <code>register</code> 添加到所有输入字段，如下所示：</p><pre><code class="language-js">&lt;Input placeholder="Enter your message" {...register('name') /&gt;</code></pre><p>现在，当点击按钮时，我们应该能够看到输入的数据。只需将数据记录到控制台，如下所示：</p><pre><code class="language-js">function submitHandler (data) {
	console.log(data);
}</code></pre><p>完成后，让我们现在为要从表单发送的 POST 请求创建一个新的 API 路由。</p><h2 id="-google-sheet-1">如何将表单连接到 Google Sheet</h2><p>在 <code>./pages/api/</code> 路由中创建一个新文件。你在此路由中创建的每个文件都是一个 api-route，它提供对所有 Node.js 功能的访问。</p><p>继续在路由中创建一个新文件，比如 <code>./pages/api/sheet.js</code>。构建一个基本的 GET 请求，看看是否一切正常：</p><pre><code class="language-js">function handler (req, res) {
	res.json({message: "It works!"});
}

export default handler;</code></pre><p>要检查 API 请求此时是否有效，请转到 <code>http://localhost:3000/api/sheet</code>。</p><p>完成后，让我们首先使用本机 <code>Fetch</code> 方法设置要从前端发送的 POST 请求。</p><pre><code class="language-js">async function submitHandler (data) {
	const response = await fetch("/api/sheet", {
    		method: "POST",
        	body: JSON.stringify(data),
        	headers: {
        		'Content-Type': 'application/json',
      		},
    	})
}</code></pre><p>在做任何其他事情之前，我们必须下载另一个包：</p><pre><code class="language-bash">npm install googleapis</code></pre><p>在 API-route（<code>/pages/api/sheet</code>）上，解构我们从前端得到的数据。</p><pre><code class="language-js">import {google} from "googleapis"

async function handler (req, res) {
    if (req.method === "POST"){
    		const {name, message} = req.body;
		res.json({message: "It works!"});
    }
}

export default handler;</code></pre><p>注意：默认情况下，API 路由将侦听 GET 请求，所以我们必须明确地检查该方法是否是一个 POST 请求。</p><p>在继续之前，还有最后一件事要设置，那就是环境变量。在我们创建新密钥时，打开包含所有 credential 的 JSON 文件。</p><p>在根目录中创建一个新文件 <code>.env.local</code>，继续，并输入以下变量。</p><pre><code class="language-env">CLIENT_EMAIL=yourclientemail
CLIENT_ID=yourclientid
PRIVATE_KEY=yourprivatekey
SPREADSHEET_ID=yourspreadsheetid
</code></pre><p>完成后，我们几乎完成了设置 API 路由来处理我们将发送到 Google Sheet 的请求。首先，让我们创建一个身份验证令牌：</p><pre><code class="language-js">const auth = new google.auth.GoogleAuth({
    credentials: {
      client_email: process.env.CLIENT_EMAIL,
      client_id: process.env.CLIENT_ID,
      private_key: process.env.PRIVATE_KEY.replace(/\\n/g, '\n'),
    },
    scopes: [
      'https://www.googleapis.com/auth/drive',
      'https://www.googleapis.com/auth/drive.file',
      'https://www.googleapis.com/auth/spreadsheets',
    ],
  });
</code></pre><p>要访问 Google Sheet，我们的应用程序首先需要提供一些范围——通常是读写访问权限。</p><p>你可以在官方<a href="https://developers.google.com/sheets/api/guides/authorizing"> Google 表单文档</a>中找到有关范围的更多信息。</p><p>你可能想知道我在第三个环境变量中使用的 <code>replace</code> 方法。这是由于我之前遇到的一个典型错误。浏览 Stack Overflow 后，我终于找到了解决方案。看起来 <code>PRIVATE_KEY</code> 需要通过删除原始密钥中的斜杠来正确解析。这可以使用替换方法轻松解决。</p><p>你可以在我创建的这个<a href="https://github.com/leerob/leerob.io/pull/342">拉取请求</a>中找到这个错误。</p><p>接下来，传入身份验证令牌并指定 API 的版本。最新的是 v4。</p><pre><code class="language-js">const sheets = google.sheets({
    auth,
    version: 'v4',
  });</code></pre><p>然后我们调用 <code>spreadsheets.value.append</code> 方法将用户条目附加到电子表单的单元格中。</p><pre><code class="language-js">const response = await sheets.spreadsheets.values.append({
      spreadsheetId: process.env.DATABASE_ID,
      range: 'Sheet1!A2:C',
      valueInputOption: 'USER_ENTERED',
      requestBody: {
        values: [[name, message]],
      },
    });</code></pre><p>你可以从 URL 本身找到电子表单 ID：</p><pre><code class="language-url">https://docs.google.com/spreadsheets/d/{spreadsheetID}/edit#gid=0</code></pre><p>范围决定了应用程序必须读取或写入的行和列。如果你对如何查找范围感到困惑，你可以通过用户界面使用 Google 表单本身来确定它。</p><figure class="kg-card kg-image-card"><img src="https://www.freecodecamp.org/news/content/images/2021/09/select_range.png" class="kg-image" alt="select_range" width="600" height="400" loading="lazy"></figure><p>第三个属性 <code>valueInputOption</code> 确定如何将用户输入的值解析到电子表单中。</p><p>例如，如果用户输入了一个数字，那么电子表单也会将其作为数字读取。</p><p>第四个属性携带要附加到特定单元格中的数据。要附加多个值，你可以将它们全部放在一个数组中，例如在这个示例下——名称和消息。</p><p>要结束 API 路由，最后将响应发送回前端：</p><pre><code class="language-js">res.status(201).json({response, result: "Feedback posted to spreadsheet!"})</code></pre><p>如果一切顺利，你应该能够发出 POST 请求，并成功地将新的单元格值添加到电子表单中。</p><h1 id="--1"><strong>总结</strong></h1><p>恭喜！你已准备好开始收集反馈。你可以在你的网站上创建自己的反馈表，或者你可以与现有服务（如 Typeform）集成。</p><p>但是如果你希望将反馈表保留在你的网站上，在你的页面上，这就是与 Google Sheets API 集成的用武之地。</p><p>Google Sheets API 非常基础——它可以读取和写入电子表格。此外，它是完全免费的，尽管你在特定时间范围内可以发出的 API 请求有限制。</p><p>因此，Google Sheets API 非常适合小型应用程序和受众较少的平台。如果你有任何问题，请在 <a href="https://twitter.com/BrodasGeo">Twitter</a> 上联系我。</p> ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ 数据验证——使用示例 JavaScript 代码检查 HTML 表单上的用户输入 ]]>
                </title>
                <description>
                    <![CDATA[ 表单在 Web 应用程序中无处不在。一些应用程序使用表单来收集数据，实现用户注册和收集电子邮件地址。也有一些应用程序使用表单来完成在线交易，以促进购物体验。 你可能会使用一些网络表单来申请新汽车贷款或者订购披萨饼作为晚餐。因此，将这些表单收集的数据进行清理，正确地格式化，避免包含任何恶意代码，这个过程很重要，它被称为“表单验证”。 当我们接受用户输入时，都需要进行表单验证。我们必须确保输入的数据格式正确，在有效数据范围内（例如日期字段），并且不包含可能导致 SQL 注入的恶意代码。数据格式错误或丢失也可能导致 API 引发错误。 表单验证有哪些不同类型？ 表单验证可以在客户端和服务器端进行。 客户端验证使用 HTML5 属性和客户端 JavaScript 进行。 你可能已经注意到，在某些表单中，如果输入无效的电子邮件地址，表单就会出现错误“请输入有效的电子邮件”。 这种直接的验证类型通常是通过客户端 JavaScript 来完成的。 在其他情况下，你可能已经注意到，当你填写表格并输入诸如信用卡之类的详细信息时，屏幕可能显示加载，然后显示错误“此信用卡无效”。 这是因为表单 ]]>
                </description>
                <link>https://www.freecodecamp.org/chinese/news/form-validation-with-html5-and-javascript/</link>
                <guid isPermaLink="false">60822483045e7705d70113a5</guid>
                
                    <category>
                        <![CDATA[ 表单 ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Chengjun.L ]]>
                </dc:creator>
                <pubDate>Fri, 23 Apr 2021 09:20:00 +0000</pubDate>
                <media:content url="https://chinese.freecodecamp.org/news/content/images/2021/04/photo-1554252116-30abdf759321.jpeg" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>表单在 Web 应用程序中无处不在。一些应用程序使用表单来收集数据，实现用户注册和收集电子邮件地址。也有一些应用程序使用表单来完成在线交易，以促进购物体验。</p><p>你可能会使用一些网络表单来申请新汽车贷款或者订购披萨饼作为晚餐。因此，将这些表单收集的数据进行清理，正确地格式化，避免包含任何恶意代码，这个过程很重要，它被称为“表单验证”。</p><p>当我们接受用户输入时，都需要进行表单验证。我们必须确保输入的数据格式正确，在有效数据范围内（例如日期字段），并且不包含可能导致 SQL 注入的恶意代码。数据格式错误或丢失也可能导致 API 引发错误。</p><h2 id="-">表单验证有哪些不同类型？</h2><p>表单验证可以在客户端和服务器端进行。</p><p>客户端验证使用 HTML5 属性和客户端 JavaScript 进行。</p><p>你可能已经注意到，在某些表单中，如果输入无效的电子邮件地址，表单就会出现错误“请输入有效的电子邮件”。 这种直接的验证类型通常是通过客户端 JavaScript 来完成的。</p><figure class="kg-card kg-image-card"><img src="https://chinese.freecodecamp.org/news/content/images/2021/04/1.gif" class="kg-image" alt="1" width="600" height="287" loading="lazy"></figure><p>在其他情况下，你可能已经注意到，当你填写表格并输入诸如信用卡之类的详细信息时，屏幕可能显示加载，然后显示错误“此信用卡无效”。</p><p>这是因为表单调用了其服务器端代码，并在执行了信用卡检查之后返回了验证错误。 进行服务器端调用的这种验证情况被称为“服务器端验证”。</p><h2 id="--1"><strong>应该验证什么数据？</strong></h2><p>当你接受来自用户的数据时，都需要进行表单验证。这可能包括：</p><ul><li>验证字段格式，例如电子邮件地址、电话号码、邮政编码、名称、密码</li><li>验证必填字段</li><li>检查诸如字符串与数字之类的数据类型，以查找诸如社会保险号之类的字段</li><li>确保输入的值是有效值，例如国家/地区、日期等</li></ul><h2 id="--2"><strong>如何设置客户端验证</strong></h2><p>在客户端，可以通过两种方式完成验证：</p><ul><li>使用 HTML5 功能</li><li>使用 JavaScript</li></ul><h3 id="-html5-">如何使用 HTML5 功能设置验证</h3><p>HTML5 提供了一堆属性来帮助验证数据。以下是一些常见的验证案例：</p><ul><li>使用 <code>required</code> 来定义必填字段</li><li>限制数据长度</li><li><code>minlength</code>、<code>maxlength</code>：用于文本数据</li><li><code>min</code> 和 <code>max</code>表示数值类型的最大值</li><li>使用 <code>type</code> 限制数据类型</li><li><code>&lt;input type="email" name="multiple&gt;</code></li><li>使用 <code>pattern</code> 指定数据模式</li><li>指定输入的表单数据需要匹配的正则表达式</li></ul><p>当输入值与上述 HTML5 验证匹配时，将为它分配一个 psuedo-class：<code>:valid</code>，否则就是 <code>:invalid</code>。</p><p>看一下这个例子：</p><pre><code class="language-html">&lt;form&gt;
&lt;label for="firstname"&gt; First Name: &lt;/label&gt;
&lt;input type="text" name="firstname" id="firstname" required maxlength="45"&gt;
&lt;label for="lastname"&gt; Last Name: &lt;/label&gt;
&lt;input type="text" name="lastname" id="lastname" required maxlength="45"&gt;
&lt;button&gt;Submit&lt;/button&gt;
&lt;/form&gt;
</code></pre><figure class="kg-card kg-image-card"><img src="https://chinese.freecodecamp.org/news/content/images/2021/04/2.png" class="kg-image" alt="2" width="1232" height="252" loading="lazy"></figure><p><a href="https://jsfiddle.net/58xc2qyj/">链接到 JSFiddle</a></p><p>在这里，我们有两个必填字段——名和姓。在 JSFidle 中尝试这个示例。如果你跳过这两个字段中的任何一个并按提交，你将收到一条消息，“请填写此字段”。这是使用内置 HTML5 进行的验证。</p><h3 id="-javascript-">如何使用 JavaScript 设置验证</h3><p>进行表单验证时，需要考虑以下几点：</p><ul><li>什么被定义为“有效”数据？ 这可以帮助你回答有关格式、长度、必填字段和数据类型的问题。</li><li>输入无效数据会怎样？这将帮助你定义验证的用户体验——是在行内显示错误消息还是在表单顶部显示错误消息，错误消息的详细程度，表单是否提交，是否应进行分析以跟踪无效格式数据的，等等。</li></ul><p>你可以通过两种方式执行 JavaScript 验证：</p><ul><li>使用 JavaScript 进行内联验证</li><li>HTML5 约束验证 API</li></ul><h4 id="-javascript--1"><strong>使用 JavaScript 进行内联验证</strong></h4><pre><code class="language-html">&lt;form id="form"&gt;
  &lt;label for="firstname"&gt; First Name* &lt;/label&gt;
  &lt;input type="text" name="firstname" id="firstname" /&gt;
  &lt;button id="submit"&gt;Submit&lt;/button&gt;

  &lt;span role="alert" id="nameError" aria-hidden="true"&gt;
    Please enter First Name
  &lt;/span&gt;
&lt;/form&gt;
</code></pre><pre><code class="language-javascript">const submit = document.getElementById("submit");

submit.addEventListener("click", validate);

function validate(e) {
  e.preventDefault();

  const firstNameField = document.getElementById("firstname");
  let valid = true;

  if (!firstNameField.value) {
    const nameError = document.getElementById("nameError");
    nameError.classList.add("visible");
    firstNameField.classList.add("invalid");
    nameError.setAttribute("aria-hidden", false);
    nameError.setAttribute("aria-invalid", true);
  }
  return valid;
}
</code></pre><pre><code class="language-css">#nameError {
  display: none;
  font-size: 0.8em;
}

#nameError.visible {
  display: block;
}

input.invalid {
  border-color: red;
}
</code></pre><p><a href="https://jsfiddle.net/0tq3e49w/4/">链接到 JSFiddle</a></p><p>在这个示例中，我们使用 JavaScript 检查必填字段。如果不存在必填字段，我们将使用 CSS 来显示错误消息。</p><p>相应地修改了 Aria 标签，以表示错误。通过使用 CSS 显示/隐藏错误，我们减少了需要进行的 DOM 操作数量。 错误消息是在上下文中提供的，从而使用户体验更加直观。</p><p><strong>HTML5 约束验证 API</strong></p><p>HTML 的 <code>required</code> 和 <code>pattern</code> 属性可以帮助执行基本验证。但是，如果你想要更复杂的验证，或想要提供详细的错误消息，则可以使用约束验证 API。</p><p>这个 API 提供的一些方法：</p><ol><li><code>checkValidity</code></li><li><code>setCustomValidity</code></li><li><code>reportValidity</code></li></ol><p>下面这些属性很有用：</p><ol><li><code>validity</code></li><li><code>validationMessage</code></li><li><code>willValidate</code></li></ol><p>在这个示例中，我们将使用 HTML5 内置方法（例如 <code>required</code> 和 <code>length</code>）结合约束验证 API进行验证，以提供详细的错误消息。</p><pre><code class="language-html">&lt;form&gt;
&lt;label for="firstname"&gt; First Name: &lt;/label&gt;
&lt;input type="text" name="firstname" required id="firstname"&gt;
&lt;button&gt;Submit&lt;/button&gt;
&lt;/form&gt;
</code></pre><pre><code class="language-javascript">const nameField = document.querySelector("input");

nameField.addEventListener("input", () =&gt; {
  nameField.setCustomValidity("");
  nameField.checkValidity();
  console.log(nameField.checkValidity());
});

nameField.addEventListener("invalid", () =&gt; {
  nameField.setCustomValidity("Please fill in your First Name.");
});
</code></pre><p><a href="https://jsfiddle.net/xz2wjLck/1/">链接到 JSFiddle</a></p><h2 id="--3">不要忘记服务器端验证</h2><p>除了客户端验证，你还必须在服务器端代码上验证从客户端收到的数据，以确保数据与你期望的数据匹配。</p><p>你还可以使用服务器端验证来执行不应在客户端上进行的业务逻辑验证。</p><h2 id="--4"><strong>表单验证最佳实践</strong></h2><ol><li>始终进行服务器端验证，因为恶意行为者可以绕过客户端验证</li><li>在产生错误的字段的上下文中提供详细的错误消息</li><li>提供一个示例，以显示出现错误消息时数据的外观，例如：“电子邮件与格式不匹配——<a href="mailto:test@example.com">test@example.com</a>”</li><li>避免使用涉及重定向的单个错误页面——这是糟糕的用户体验，迫使用户返回上一页以修复表单并丢失上下文</li><li>始终标记必填字段</li></ol><p>原文：<a href="https://www.freecodecamp.org/news/form-validation-with-html5-and-javascript/">Data Validation – How to Check User Input on HTML Forms with Example JavaScript Code</a>，作者：<a href="https://www.freecodecamp.org/news/author/shrutikapoor08/">Shruti Kapoor</a></p> ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ 更强大的表单控件 ]]>
                </title>
                <description>
                    <![CDATA[ 有了一个新事件，再加上一些自定义元素 API，表单的使用变得更加容易。 很多开发者构建自定义表单元素，提供浏览器未内置的控件，或自定义超越内置表单控件的外观与体验。 然而，复制 HTML 内置表单控件的特性很难。想一下当你把一个 <input> 元素加进一个表单时，它自动获得的一些特性：  * 输入框自动加进表单的控件列表  * 输入框中的值自动与表单一起提交  * 输入框参与表单校验    [https://developer.mozilla.org/zh-CN/docs/Learn/HTML/Forms/Data_form_validation]    ，你可以用 :valid 和 :invalid 伪类为输入框设置样式  * 表单重置、重加载时，或浏览器尝试自动填充表单项时，输入框都会被通知 自定义表单组件通常没有这些特性。开发者能用 JavaScript 解决一些限制，比如向一个表单添加一个隐藏的 <input>  以参与表单提交。但其它特性无法单单用 JavaScript 复制。 两个 Web 新特性让构建自定义表单控件更简单，解决了现有自定义控件的限制：  *  ]]>
                </description>
                <link>https://www.freecodecamp.org/chinese/news/better-form-controls/</link>
                <guid isPermaLink="false">5ff71d2739641a0517d5352f</guid>
                
                    <category>
                        <![CDATA[ JavaScript ]]>
                    </category>
                
                    <category>
                        <![CDATA[ 表单 ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Web开发 ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ TechQuery ]]>
                </dc:creator>
                <pubDate>Thu, 07 Jan 2021 09:20:00 +0000</pubDate>
                <media:content url="https://chinese.freecodecamp.org/news/content/images/2021/01/ratapan-anantawat-8EiFzjvt_U0-unsplash--1-.jpg" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>有了一个新事件，再加上一些自定义元素 API，表单的使用变得更加容易。</p><p>很多开发者构建自定义表单元素，提供浏览器未内置的控件，或自定义超越内置表单控件的外观与体验。</p><p>然而，复制 HTML 内置表单控件的特性很难。想一下当你把一个 <code>&lt;input&gt;</code> 元素加进一个表单时，它自动获得的一些特性：</p><ul><li>输入框自动加进表单的控件列表</li><li>输入框中的值自动与表单一起提交</li><li>输入框参与<a href="https://developer.mozilla.org/zh-CN/docs/Learn/HTML/Forms/Data_form_validation" rel="noopener">表单校验</a>，你可以用 <code>:valid</code> 和 <code>:invalid</code> 伪类为输入框设置样式</li><li>表单重置、重加载时，或浏览器尝试自动填充表单项时，输入框都会被通知</li></ul><p>自定义表单组件通常没有这些特性。开发者能用 JavaScript 解决一些限制，比如向一个表单添加一个隐藏的 <code>&lt;input&gt;</code> 以参与表单提交。但其它特性无法单单用 JavaScript 复制。</p><p>两个 Web 新特性让构建自定义表单控件更简单，解决了现有自定义控件的限制：</p><ul><li><code>formdata</code> 时间让一个任意的 JavaScript 对象参与到表单提交中，所以你可以无需一个隐藏的 <code>&lt;input&gt;</code> 就能添加表单数据</li><li><strong><strong>Form-associated Custom Elements API</strong></strong> 让自定义元素表现得更像内置表单控件</li></ul><p>这两个特性可用于创建效果更好的新型控件。</p><p>构建自定义表单组件是个高级话题。本文假定您对表单和表单控件有一定的了解。当构建一个自定义表单控件时，有很多因素要考虑，特别是确定你的控件是对所有用户无障碍的。学习更多表单知识，前往 <a href="https://developer.mozilla.org/zh-CN/docs/Learn/HTML/Forms" rel="noopener">MDN 表单指南</a>。</p><h2 id="-api"><strong>基于事件的 API</strong></h2><p><code>formdata</code> 事件是一个让任何 JavaScript 代码参与表单提交的底层 API。该机制是这样的：</p><ol><li>你添加一个 <code>formdata</code> 事件监听器到你想交互的表单</li><li>当一个用户点击提交按钮，该表单触发一个 <code>formdata</code> 事件，它包含一个持有所有待提交数据的 <a href="https://developer.mozilla.org/zh-CN/docs/Web/API/FormData" rel="noopener"><code>FormData</code></a> 对象</li><li>每个 <code>formdata</code> 监听器都有机会在表单提交前添加或修改数据</li></ol><p>这里是一个在 <code>formdata</code> 事件监听器中发送一个单值的例子：</p><pre><code class="language-javascript">const form = document.querySelector('form');
// FormData 事件在 &lt;form&gt; 提交时、传输前触发
// 该事件有个 formData 属性
form.addEventListener('formdata', ({ formData }) =&gt; {
  // https://developer.mozilla.org/zh-CN/docs/Web/API/FormData
  formData.append('my-input', myInputValue);
});</code></pre><p>用我们在 Glitch 的示例来尝试。务必在 Chrome 77 或更高版本上运行，以查看该 API 的运行情况：<a href="https://glitch.com/~nosy-scandalous-king">https://glitch.com/~nosy-scandalous-king</a>。</p><h2 id="-"><strong>表单关联的自定义元素</strong></h2><p>你可以把基于事件的 API 用于任何类型的组件，但它只允许你与提交过程交互。</p><p>标准化的表单控件除了提交外，还参与了表单生命周期的许多部分。表单关联的自定义元素旨在填补自定义控件和内置控件的鸿沟，并匹配了很多标准表单元素的特性：</p><ul><li>当你把一个表单关联的自定义元素放进一个 <code>&lt;form&gt;</code>，它就像一个浏览器提供的控件，自动与该表单关联。</li><li>该元素可被一个 <code>&lt;label&gt;</code> 元素标记</li><li>该元素可设置一个与表单一起自动提交的值</li><li>该元素可设置一个标记，指示它是否取得有效输入。如果其中一个表单元素有无效输入，该表单则不能被提交。</li><li>该元素可提供一些用于表单生命周期多个部分的回调 —— 比如当该表单被禁用或重置到它的默认状态</li><li>该元素支持标准的 CSS 表单控件伪类，如 <code>:disabled</code> 和 <code>:invalid</code></li></ul><p>这么多功能！本文不会涉及所有内容，但将阐述把自定义元素与表单集成所需的基础知识。</p><p>本节假设你对自定义元素有基本的了解。有关自定义元素的介绍，参见 Web Fundamentals 上的<a href="https://developers.google.com/web/fundamentals/web-components/customelements" rel="noopener">《Custom Elements v1：可复用的 Web 组件》</a>。</p><h3 id="--1"><strong>定义一个表单关联的自定义元素</strong></h3><p>把一个自定义元素转变成一个表单关联的自定义元素，需要几个额外步骤:</p><ul><li>添加一个静态 <code>formAssociated</code> 属性到你的自定义元素类，这告诉浏览器把该元素看作一个表单控件</li><li>在该元素上调用 <code>attachInternals()</code> 方法，以访问表单控件的其它方法和属性，如 <code>setFormValue()</code> 和 <code>setValidity()</code></li><li>添加表单控件支持的通用属性和方法，如 <code>name</code>、<code>value</code> 和 <code>validity</code></li></ul><p>来看看如何把这些项目融入一个基础的自定义元素定义：</p><pre><code class="language-javascript">// 表单关联的自定义元素必须是独立的自定义元素 ——
// 意味着它们必须继承自 HTMLElement，而非它的一个子类。
class MyCounter extends HTMLElement {
  // 把该元素标记为一个表单关联的自定义元素
  static formAssociated = true;

  constructor() {
    super();
    // 获得访问内部表单控件 API 的能力
    this.internals_ = this.attachInternals();
    // 该控件的内部值
    this.value_ = 0;
  }
  // 表单控件通常暴露一个“value”属性
  get value() {
    return this.value_;
  }
  set value(v) {
    this.value_ = v;
  }
  // 以下属性和方法并非必须，但浏览器级表单控件提供了它们。
  // 提供它们有助于确保与浏览器提供的控件保持一致。
  get form() {
    return this.internals_.form;
  }
  get name() {
    return this.getAttribute('name');
  }
  get type() {
    return this.localName;
  }
  get validity() {
    return this.internals_.validity;
  }
  get validationMessage() {
    return this.internals_.validationMessage;
  }
  get willValidate() {
    return this.internals_.willValidate;
  }

  checkValidity() {
    return this.internals_.checkValidity();
  }
  reportValidity() {
    return this.internals_.reportValidity();
  }
  // ...
}

customElements.define('my-counter', MyCounter);</code></pre><p>一旦注册，你就可以在你使用浏览器表单控件的任何地方使用这个控件：</p><pre><code class="language-html">&lt;form&gt;
  &lt;label&gt;兔子的数量: &lt;my-counter&gt;&lt;/my-counter&gt;&lt;/label&gt;
  &lt;button type="submit"&gt;提交&lt;/button&gt;
&lt;/form&gt;</code></pre><h3 id="--2"><strong>设置一个值</strong></h3><p><code>attachInternals()</code> 方法返回一个可访问表单控件 API 的 <code>ElementInternals</code> 对象。这些 API 中最基础的是 <code>setFormValue()</code> 方法，用来设置该控件的当前值。<code>setFormValue()</code> 方法可接受三种类型之一的值：</p><ul><li>一个字符串值</li><li>一个 <a href="https://developer.mozilla.org/zh-CN/docs/Web/API/File" rel="noopener"><code>File</code></a> 对象</li><li>一个 <a href="https://developer.mozilla.org/zh-CN/docs/Web/API/FormData" rel="noopener"><code>FormData</code></a> 对象，你可以用一个 <code>FormData</code> 对象来传多值（例如一个信用卡输入控件可能会传一个卡号、过期日期和验证码）</li></ul><p>设置一个简单值：</p><pre><code class="language-javascript">this.internals_.setFormValue(this.value_);</code></pre><p>设置多值，你可以执行以下操作：</p><pre><code class="language-javascript">// 用该控件名作为提交数据的基础名
const n = this.getAttribute('name');
const entries = new FormData();

entries.append(n + '-first-name', this.firstName_);
entries.append(n + '-last-name', this.lastName_);
this.internals_.setFormValue(entries);</code></pre><p><code>setFormValue()</code> 方法接受一个可选的第二参数 <code>state</code>，用来存储该控件的内部状态。<br>详情参见本文「恢复表单状态」一节</p><h3 id="--3"><strong>输入校验</strong></h3><p>你的控件也可以通过调用内部对象上的 <code>setValidity()</code> 方法参与表单校验。</p><pre><code class="language-javascript">// 假设在内部值更新时调用此方法
onUpdateValue() {
    if (
        !this.matches(':disabled') &amp;&amp;
        this.hasAttribute('required') &amp;&amp;
        this.value_ &lt; 0
    ) {
        this.internals_.setValidity(
            { customError: true }, 'Value cannot be negative.'
        );
    } else {
        this.internals_.setValidity({});
    }
    this.internals.setFormValue(this.value_);
}</code></pre><p>你可以用 <code>:valid</code> 和 <code>:invalid</code> 伪类给一个表单关联的自定义元素设置样式，就像一个内置表单控件一样。</p><p>尽管你可以设置一个校验消息，但 Chrome 目前不能显示表单关联自定义元素的校验消息。</p><h3 id="--4"><strong>生命周期回调</strong></h3><p>Form-associated Custom Elements API 包含一组额外的生命周期回调，以绑定到表单生命周期。</p><p>这些回调是可选的：仅在你的元素需要在生命周期的某一刻做某事时才实现一个回调。</p><h4 id="void-formassociatedcallback-form-"><strong><code>void formAssociatedCallback(form)</code></strong></h4><p>当浏览器将一个表单元素关联到该元素时调用，或从一个表单元素解除关联该元素时。</p><h4 id="void-formdisabledcallback-disabled-"><strong><code>void formDisabledCallback(disabled)</code></strong></h4><p>该元素的 <code>disabled</code> 状态改变后，无论是因为该元素的 <code>disabled</code> 属性被添加或移除；还是因为该元素的一个祖先 <code>&lt;fieldset&gt;</code> 的 <code>disabled</code> 状态被改变。<code>disabled</code> 参数代表该元素的新<a href="https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#concept-fe-disabled" rel="noopener">禁用状态</a>。例如，当该元素被禁用时，它可能要禁用它影子 DOM 中的元素。</p><h4 id="void-formresetcallback-"><strong><code>void formResetCallback()</code></strong></h4><p>该表单被重置后调用。该元素应该将其自身重置回某种默认状态。对于 <code>&lt;input /&gt;</code> 元素，这通常涉及设置 <code>value</code> 属性以匹配标签中设置的 <code>value</code> 属性（或在复选框的案例中，设置 <code>checked</code> 对象属性以匹配 <code>checked</code> 标签属性）。</p><h4 id="void-formstaterestorecallback-state-mode-"><strong><code>void formStateRestoreCallback(state, mode)</code></strong></h4><p>在两种情形之一被调用：</p><ul><li>当浏览器恢复该元素状态时（例如，一次导航后，或当浏览器重启时），在此情形下 <code>mode</code> 参数是 <code>"restore"</code></li><li>当浏览器的输入助手特性（诸如表单自动填充）设置一个值时，在此情形下 <code>mode</code> 参数是 <code>"autocomplete"</code></li></ul><p>第一个参数的类型则取决于 <code>setFormValue()</code> 方法被怎样调用。详情参见本文「恢复表单状态」一节。</p><h3 id="--5"><strong>恢复表单状态</strong></h3><p>在某些情形下 —— 如当导航返回一个页面时，或重启浏览器时，浏览器可能尝试恢复该表单到用户保留的状态。</p><p>对于一个表单关联的自定义元素，被恢复的状态来自你传到 <code>setFormValue()</code> 方法的值。就像早前示例中展示的那样，你可以用一个单值参数或两个参数调用该方法：</p><pre><code class="language-javascript">this.internals_.setFormValue(value, state);</code></pre><p>此处的 <code>value</code> 代表该控件的可提交参数。此处的可选 <code>state</code> 参数是一个该控件状态的 <em><em>内部</em></em> 表示，可包含不发送到服务器的数据。该 <code>state</code> 参数具有与 <code>value</code> 参数相同的类型 —— 它可以是一个字符串、<code>File</code> 或 <code>FormData</code> 对象。</p><p><code>state</code> 参数在你无法只基于值去恢复一个控件状态时很有用。例如，假设你创建了一个多模的颜色选择器：一个调色板或 RGB 色轮。可提交的 <em><em>值</em></em> 会是规范形式的选中颜色，如 <code>"#7fff00"</code>。但恢复该控件到一个特定状态，你也需要知道它之前处在哪种模式，所以 <em><em>状态</em></em> 可能形如 <code>"palette/#7fff00"</code>。</p><pre><code class="language-javascript">this.internals_.setFormValue(this.value_, this.mode_ + '/' + this.value_);</code></pre><p>你的代码可能需要基于存储的状态值来恢复自身状态。</p><pre><code class="language-javascript">formStateRestoreCallback(state, mode) {
    if (mode == 'restore') {
        // 预期一个形如 'controlMode/value' 的状态参数
        [controlMode, value]= state.split('/');
        this.mode_ = controlMode;
        this.value_ = value;
    }
    // Chrome 目前不处理表单关联自定义元素的自动填充。
    // 在自动填充案例中，你可能需要处理一个原始值。
}</code></pre><p>在一个更简单的控件案例中（例如一个数字输入框），值可能足够该控件恢复之前的状态。当调用 <code>setFormValue()</code> 时，若你忽略 <code>state</code> ，值会被直接传给 <code>formStateRestoreCallback()</code>。</p><pre><code class="language-javascript">formStateRestoreCallback(state, mode) {
    // Simple case, restore the saved value
    this.value_ = state;
}</code></pre><h3 id="--6"><strong>一个实际的例子</strong></h3><p>这个<a href="https://glitch.com/~form-associated-ce">示例</a>整合了表单关联自定义元素的很多特性。务必在 Chrome 77 或更高版本运行它，以查看 API 的运行情况。</p><h2 id="--7"><strong>特性检测</strong></h2><p>你可以用特性检测去确定 <code>formdata</code> 事件和表单关联的自定义元素是可用的。目前每个特性都没有补丁发布。针对这两种情况，你可以回退到添加一个隐藏的表单元素来把该控件值传给表单。很多更高级的表单关联自定义元素特性将可能很难或无法打补丁。</p><pre><code class="language-javascript">if ('FormDataEvent' in window) {
  // 支持 formdata 事件
}
if (
  'ElementInternals' in window &amp;&amp;
  'setFormValue' in window.ElementInternals.prototype
) {
  // 支持表单关联的自定义元素
}</code></pre><p><strong><strong>译注</strong></strong>：原文发表一季度后，社区开发者发布了一个 <a href="https://github.com/calebdwilliams/element-internals-polyfill" rel="noopener">ElementInternals polyfill</a>，而 Web Components 标准团队官方 polyfill 也在<a href="https://github.com/webcomponents/polyfills#roadmap" rel="noopener">计划实现本文所述特性</a>。</p><h2 id="--8"><strong>结论</strong></h2><p><code>formdata</code> 事件和表单关联自定义元素为创建自定义表单控件提供了新工具。</p><p><code>formdata</code> 事件没有给你任何新能力，但它给你一个接口，让你无须一个隐藏的 <code>&lt;input /&gt;</code> 元素，即可添加表单数据到提交流程。</p><p>Form-associated Custom Elements API 通过一组新能力让自定义表单控件像内置表单控件一样工作。</p><h2 id="--9"><strong>译者后记</strong></h2><p>Custom Elements v1 标准刚发布时，自定义表单元素的构建可以通过<strong><strong>扩展原生标签</strong></strong>特性来实现：</p><pre><code class="language-html">&lt;template&gt;
  &lt;div contenteditable="true"&gt;&lt;/div&gt;
&lt;/template&gt;

&lt;script&gt;
  const { content } = document.currentScript.previousElementSibling;

  class MyInput extends HTMLInputElement {
    constructor() {
      super().attachShadow({ mode: 'open' }).append(content.cloneNode(true));
    }
  }
  customElements.define('my-input', MyInput, { extends: 'input' });
&lt;/script&gt;

&lt;input is="my-input" /&gt;</code></pre><p>虽然基于 <strong><strong>ES 6 class 继承</strong></strong>语法可以重写表单元素的各种属性、方法，在外部代码操作 DOM 时执行自定义逻辑，但用户直接与浏览器交互不会调用这些重写的接口。因此我们的自定义元素只能单纯地依赖内置表单元素的能力，而无法介入表单的工作流程。</p><p>于是，浏览器进一步暴露自己的内部机制，便有了 Element Internals API。它除了包含本文所述 Form-associated Custom Element API 的很多接口，还涉及另一项新标准 —— <a href="https://w3c.github.io/aria/#ARIAMixin" rel="noopener">AOM</a>（可访问性/无障碍化对象模型），让开发者更轻松地构造更好用的组件。</p><figure class="kg-card kg-image-card"><img src="https://web-cell.dev/WebCell-1.fb612fdb.png" class="kg-image" alt="WebCell-1.fb612fdb" width="600" height="400" loading="lazy"></figure><p>同时，译者自研的 <a href="https://web-cell.dev/" rel="noopener">Web Components 组件框架 WebCell</a> 在原文的帮助下<a href="https://github.com/calebdwilliams/element-internals-polyfill/network/dependents" rel="noopener">率先支持新标准</a>，使前文那段最长的示例代码变得非常简单：</p><pre><code class="language-typescript">import { component, mixinForm } from 'web-cell';

@component({
  tagName: 'my-counter'
})
export class MyCounter extends mixinForm() {}</code></pre><p>最后，欢迎大家持续关注译者对 Web 组件相关标准的后续译文和开源项目更新~</p><p>本文译自 <a href="https://web.dev/more-capable-form-controls/">More capable form controls</a>。访问译者的<a href="https://tech-query.me/">更多文章</a>。</p> ]]>
                </content:encoded>
            </item>
        
    </channel>
</rss>
