将用户的国家 /地区移至具有NetLify Edge功能和地理位置的选择元素的顶部
#javascript #html #教程 #netlify

想象一下我们有一些生成的HTML,它可能是一些手写的静态HTML,它可能是Drupal或WordPress之类的CM的输出,它可以由Astro或Hipty等工具创建,也可以由任何框架创建这会发送适当的HTML响应(不仅是<div id="root"></div>到浏览器。也许HTML是结帐表,联系表格,或任何其他通过select元素或“下拉列表”收集用户国家/地区的表格。

如果用户的国家始终处于列表的顶部,这样他们就可以轻松地找到它而无需任何滚动或打字了吗?

我们可以get the user's latitude and longitude with JavaScript,但是用户必须允许。征求许可进行地理位置,而无需解释为什么需要它或不将其绑定到用户的互动(例如用户在商店查找器或映射应用程序中单击'最接近我的位置'的按钮)是一个不,不。另外,我们仍然必须使用API​​将纬度和经度变成代表一个国家的事物。

如果您的网站有后端,那么您可以编写一些自定义代码以将访问者的IP转换为国家 /地区代码,或者可能会找到插件来执行此操作,但这可能涉及支付GeoLocation API订阅,购买,购买,购买,购买,购买,编写,或至少安装插件,并使用宝贵的开发资源。另外,如果您的HTML是手写的,来自第三方或来自您不想承担维护责任的系统,那么您将无法在申请级。您可能还会遇到自定义代码的某些问题,实际上并未访问用户的IP地址,但是如果HTML响应被缓存,则CDN,LOAD BALANCER或FIREWALL的IP地址,甚至更糟的是,其他用户的IP地址。

相反,我们可以使用边缘函数从Netlify的free geolocation functionality变换中受益于HTML来做我们想要的。这样,HTML来自何处或技术堆栈生产它,并且因为它在“边缘”(网络中的一个点与用户近距离)起作用,因此您不太可能遇到反向代理缓存,负载平衡器,Web应用程序防火墙等问题,因为边缘功能转换的响应已经通过了许多层。

预先填写用户的国家

我使用这个想法时的第一种方法是自动在列表中选择用户的国家 /地区。在我们谈论这种方法的问题之前,我是这样做的:

import { HTMLRewriter } from "https://ghuc.cc/worker-tools/html-rewriter@v0.1.0-pre.17/index.ts";

export default async (request, context) => {
  // Get the country code from the incoming request using the
  // `context` object provided by Netlify. This information
  // is gathered by comparing the user's IP address against
  // MaxMind's GeoIP2 database.
  const countryCode = context.geo?.country?.code;
  // Get the response provided by Netlify - this contains our
  // original HTML that we want to modify.
  const response = await context.next();

  if (!countryCode) {
    // If we don't have a country code, return the response 
    // as-is.
    return response;
  }

  // If we have a country code, use that to pre-select an 
  // option in the form by adding the `selected` attribute.
  return (
    // Use the html-rewriter tool to modify the original
    // response.
    new HTMLRewriter()
      // We use an attribute selector to find the `option`
      // element with a value that matches our user's country
      // code. Change `#country-1` to a selector that matches 
      // your target `select` element.
      .on(`#country-1 option[value="${countryCode}"]`, {
        element(element) {
          element.setAttribute("selected", "");
        },
      })
      .transform(response)
  );
};

您可以在这里看到一个示例:https://edge-country-code-select.netlify.app

边缘函数将找到代表用户国家 /地区的option(基于其IP地址),并通过在发送到浏览器之前对HTML进行修改,将其标记为选择。如果您住在英国,HTML看起来会像这样:

<select required id="country-1" name="country-1">
  <!-- all the A-U countries… -->
  <option value="GB" selected>
    United Kingdom of Great Britain and Northern Ireland
  </option>
  <!-- all the U-Z countries… -->
</select>

this 一开始就感觉到很明智,但可能会惹恼人们在旅行时浏览或与他们所居住的国家外的VPN连接。如果这些人提交表格,可能会导致数据不正确不检查预填充的值。

一种更安全的方法是避免对表单字段进行预填充,但仍然使用户可以轻松从列表中选择自己的国家。

将用户的国家登上列表

通过将地理分配的国家移至我们建议的列表的顶部,但不代表用户选择。

这是最终结果HTML的外观:

<select required id="country-2" name="country-2" data-country="GB">
  <option disabled selected value="">Select a country</option>
  <option value="GB">
    United Kingdom of Great Britain and Northern Ireland
  </option>
  <option value="" disabled>--------</option>
  <option value="AF">Afghanistan</option>
  <!-- All the A-Z countries… -->
  <option value="ZW">Zimbabwe</option>
</select>

和浏览器中的结果:

Screenshot of a rendered HTML select element with an option representing the user's country at the top of the list, then a divider option, then options for each country name

您可以在这里再次看到示例:https://edge-country-code-select.netlify.app

这是代码:

import { HTMLRewriter } from "https://ghuc.cc/worker-tools/html-rewriter@v0.1.0-pre.17/index.ts";

export default async (request, context) => {
  // Get the country code from the incoming request using the
  // `context` object provided by Netlify.
  const countryCode = context.geo?.country?.code;
  // Get the response provided by Netlify - this contains our
  // HTML.
  const response = await context.next();

  if (!countryCode) {
    // If we don't have a country code, return the response
    // as-is.
    return response;
  }

  // Return the response once it's passed through the HTML Rewriter.
  return (
    new HTMLRewriter()
      .on(`#country-2`, {
        element(element) {
          // Set a data attribute containing the country code onto the select
          // element, we'll use this next.
          element.setAttribute("data-country", countryCode);
          // Add some inline JavaScript to the page that will read the country
          // code data attribute and move the right option to the top of the list.
          const id = element.getAttribute("id");
          element.after(
            `<script>
              (() => {
                // Get the select element using whatever ID the HTML Rewriter used.
                const select = document.getElementById("${id}");
                // Get the country from the data attribute.
                const country = select.dataset.country;
                // Find the option that matches the user's country.
                const option = select.querySelector(\`option[value="\${country}"]\`);
                // Create a disabled divider option.
                const dividerOption = document.createElement("option");
                dividerOption.setAttribute("value", "");
                dividerOption.setAttribute("disabled", "");
                dividerOption.innerText = "--------";
                // Insert the divider option before the second option
                select.insertBefore(dividerOption, select.querySelector(":nth-child(3)"));
                // Insert the country option before the divider option
                select.insertBefore(option, select.querySelector(":nth-child(3)"));
              })();
            </script>`,
            { html: true }
          );
        },
      })
      .transform(response)
  );
};

这是一种略有不同的方法,我们让客户端的JavaScript完成大部分工作,而不是在我们的边缘功能中执行。我最初想在边缘函数中进行HTML的所有转换,但是由于HTML重写器工具的工作方式,我发现这很困难。它一次处理一个元素的响应,向下上下(以便可以支持流),因此很难及时回去更改以前的事物。这很重要,因为我们需要找到代表用户国家 /地区的option元素,然后将其移至列表的顶部 - 但是在那时,列表顶部的较早的option已经处理过,因此我们错过了机会。

另一种选择是进行一些替换,但这将涉及writing regexes for HTML (no thank you!)ðä¢。

另一个选项是忘记“将现有的DOM节点移动到列表的顶部,而是使用Edge函数使用context.geo.country.name的数据来创建一个全新的DOM节点。选项值。这将起作用,但是我们可能会引起问题,因为我们的边缘功能不会意识到该应用程序可能在options上设置的任何额外的HTML属性。我们也可能会遇到国家名称的问题; Netlify提供的国家名称将以英语为单位,但是如果该网站的其余部分用法语或西班牙语使用国家名称?

所有这些原因使我认为为此添加一些客户端JavaScript是可以的。毕竟,这是一种进步的增强。如果JS失败,则菜单将完全按照原本打算工作。