2024 年 9 月 20 日,星期五
作者:艾伦·贾丁

带有编辑器的地址自动完成

使用 Editor 构建数据输入表单时,开发人员可以通过多种方式让终端用户轻松进行操作。简洁清晰的表单是 Editor 的优势之一,但还可以使用查找和自动完成功能来让终端用户更轻松地录入数据。当你在互联网上注册服务或运送物品时,很可能会遇到地址自动完成功能。本文将深入探讨如何在 Editor 中实现这一功能。

示例

首先,让我们直接跳到一个工作示例,以便你了解我们将要构建的内容。下面是一个带有三个虚构地址的表格。 它完全可以按常规的 DataTable 进行编辑,使用 Editor(在本例中为本地编辑 - 不使用后端数据库)。本篇博文中感兴趣的部分是,如果你创建新记录或编辑现有记录,则第二个字段不是数据字段,而是一个输入,允许用户开始输入地址,然后将使用 出色的 Geoapify API 进行查找。 向终端用户(本例中是你!)展示了一系列选项,随着其持续输入,选项会得到改进,并允许他们单击选项之一,以自动完成表单中的字段。

姓名 房屋 街道 城市 邮政编码 国家/地区
艾莉·佐藤 艾维街 格莱布巷 伦敦 伦敦 NW11 9TU 英国
霍普·福恩特斯 392 枫树街 洛杉矶 加利福尼亚 90017 美国
塞尔日·鲍德温 28 Chemin Du Lavarin Sud 卡昂 下诺曼底 14000 法国

工作原理

可编辑的 DataTable 是一个带有七列的简单表格,Editor 使用七列加上用于地址查找的单个额外字段来匹配它。 你可以在 本网站 上找到大量有关如何设置 DataTables 和 Editor 的示例和文档。为了简洁起见,我将不会重复这里使用的配置,但一个需要注意的地方是,lookup 字段具有 fields.submit 选项,该选项设置为 false,因为该字段中的数据与提交给服务器无关,它只是个客户端字段。

用于 DataTables 和 Editor 的初始化代码为

const editor = new DataTable.Editor({
    fields: [
        {
            label: 'Name:',
            name: 'name'
        },
        {
            label: 'Address lookup:',
            labelInfo: 'Start typing your address to automatically look it up.',
            name: 'lookup',
            submit: false
        },
        {
            label: 'House name / number:',
            name: 'house'
        },
        {
            label: 'Street:',
            name: 'street'
        },
        {
            label: 'City:',
            name: 'city'
        },
        {
            label: 'County:',
            name: 'county'
        },
        {
            label: 'State:',
            name: 'state'
        },
        {
            label: 'Postcode / ZIP:',
            name: 'postcode'
        },
        {
            label: 'Country:',
            name: 'country'
        }
    ],
    table: '#addresses'
});

new DataTable('#addresses', {
    columns: [
        {
            className: 'dtr-control',
            orderable: false,
            targets: 0,
            defaultContent: ''
        },
        { data: 'name' },
        { data: 'house' },
        { data: 'street' },
        { data: 'city' },
        { data: 'county' },
        { data: 'state' },
        { data: 'postcode' },
        { data: 'country' }
    ],
    layout: {
        topStart: {
            buttons: [
                { extend: 'create', editor: editor },
                { extend: 'edit', editor: editor },
                { extend: 'remove', editor: editor }
            ]
        }
    },
    order: [1, 'asc'],
    responsive: {
        details: {
            type: 'column'
        }
    },
    select: true
});

地址查找

在 Editor 中,当你想根据字段中的输入值执行某些操作时,可以使用 dependent() 方法。 在本例中,我们希望侦听 lookup 字段上的更改,然后再触发对 Geoapify 自动完成 API(其 API 文档齐全)的调用。

这里关于我们如何使用 dependent() 有两点很重要

  1. 我们希望在用户输入时就采取操作,而不仅仅是等到用户输入完毕然后将焦点移出该字段(例如,默认情况下,更改事件)。为此,我们使用 dependent() 的第三个参数,将侦听事件设置为 input
  2. 同时,我们不希望在每次击键时都触发一个 Ajax 请求——我们希望对它们进行分组,以免浪费 API 资源。为此,我们可以使用 DataTable.util.debounce() 实用程序方法。

我们的 dependent() 设置调用的结构如下

editor.dependent(
    'lookup',
    DataTable.util.debounce(function (val, data, cb) {
        // Ajax lookup code based on `val`
    }),
    {event: 'input'}
);

既然我们已从 lookup 字段获得了一个值,我们可以转到 Geoapify API 并对其进行查询,以查看哪些选项被认为是可行的候选选项。为此,我们可以使用 fetch() API

if (val) {
    fetch(
        'https://api.geoapify.com/v1/geocode/autocomplete?text=' +
            encodeURIComponent(val) +
            '&apiKey=' +
            encodeURIComponent(apiKey)
    )
        .then((response) => response.json())
        .then((json) => {
            // Got our response
            displayResults(editor, json);
            cb({});
        });
}

请注意,apiKey 变量将被设置为你的 Geoapify 项目的任何 API 密钥(在他们的网站上注册)。此外,你会看到 cb() 被称为一个空对象——这个回调函数是 dependent() 函数的一部分,并告知 Editor 你的从属处理程序已完成处理。Editor 将展示“处理中”的字段,直到回调被调用,让最终用户知道正在发生的事情。

可选显示

从自动完成 API 中,我们现在有一些选项应该向最终用户展示。要做到这一点,我们创建一个 displayResults() 函数,它采用 API 返回的数组,为每个选项制作一个 div,然后将其插入到字段“信息”区域中。可以使用 field().fieldInfo() 方法来编写字段信息。

这一块代码乍一看可能有点吓人,但实际上它所做的只是创建了一个容器 div,然后为每个选项创建一个 div,其中包含许多 span 元素,用于每个地址元素。我还创建了一个 createElement() 函数(如下所示),以尽可能简化新元素的创建。

// Display the list of addresses that have been found
function displayResults(editor, json) {
    var options = createElement('div', { className: 'lookup-options' });
    var addresses = json.features.slice(0, 6); // Limit to 6

    addresses.forEach((full) => {
        var address = full.properties;
        var option = createElement('div', { className: 'lookup-option' }, null, options);

        // Store for lookup if selected
        option._address = address;

        createElement('span', { className: 'lookup-house' }, address.housenumber || address.name, option);
        createElement('span', { className: 'lookup-street' }, address.street, option);
        createElement('span', { className: 'lookup-city' }, address.city, option);
        createElement('span', { className: 'lookup-county' }, address.county, option);
        createElement('span', { className: 'lookup-state' }, address.state, option);
        createElement('span', { className: 'lookup-postcode' }, address.postcode, option);
        createElement('span', { className: 'lookup-country' }, address.country, option);
    });

    editor.field('lookup').fieldInfo(options);
}

// Helper for creating new DOM elements
function createElement(name, props, content, appendTo) {
    let el = document.createElement(name);

    if (props) {
        Object.assign(el, props);
    }

    if (content) {
        el.textContent = content;
    }

    if (appendTo) {
        appendTo.appendChild(el);
    }

    return el;
}

最后,CSS 用于在网格中显示地址选项。我使用了 flexbox,但你可以使用 CSS 网格或任何你想要的其他选项。实际上,使用上面的函数,你可以完全控制 DOM 结构,因此你可以根据需要修改布局以适合你的应用程序!

.lookup-options {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5em;
  justify-content: space-between;
}

.lookup-option {
  width: calc(50% - 0.25em);
  box-sizing: border-box;
  padding: 1em;
  border: 1px solid rgba(122, 122, 122, 0.5);
  border-radius: 4px;
  cursor: pointer;
}

.lookup-option span {
    display: block;
}

地址选择

我们需要完成的最后一个项目是允许最终用户点击一个地址,然后将值插入到 Editor 表单中。为此,我们在 Editor 字段上使用 DOM click 事件侦听器 (field().node()),并将其过滤为仅对选项元素 (.lookup-option) 上的事件采取操作。如果你熟悉 jQuery 术语,这称为委托事件处理程序,它基本上允许子元素的 DOM 元素发生变化(在本例中是我们的选项),同时只需要一个事件侦听器通过事件传播来处理事件(该事件通过 DOM 冒泡

// Handle a click on an address to select it
editor.field('lookup').node().addEventListener('click', function (e) {
    let option = e.target.closest('div.lookup-option');

    if (option) {
        // Write field values
    }
});

要写入字段值,我们可以简单地为每个字段使用 Editor field().val() 方法来从所选的条目中写入值。请注意在上面的 displayResults() 中,每个选项 div 都有地址对象附加到其上,作为 _address 属性。这是为了在选择选项时,我们可以通过读取该属性简单地从节点检索它

let address = option._address;

// Address was selected - fill in the Editor field values
editor.field('house').val(address.housenumber || address.name || '');
editor.field('street').val(address.street || '');
editor.field('city').val(address.city || '');
editor.field('county').val(address.county || '');
editor.field('state').val(address.state || '');
editor.field('postcode').val(address.postcode || '');
editor.field('country').val(address.country || '');

// Clear the lookup
editor.field('lookup').fieldInfo('').val('');

最后的操作是清除查找字段的选项和值。

将所有内容组合在一起

我在本文中将所有内容分解为几个块,以便能够解释每个块。要查看组合在一起的所有内容,请查看以下文件

本文档中介绍的地址自动完成功能仅仅是可在 Editor 中用于自动完成数据的示例,并且使用了特定的 API。您可能会访问不同的地址自动完成系统,或者有可执行自动完成操作的完全不同的数据类型。

希望本文档为您提供了在自己的表单中添加类似操作所需工具并让您充满信心。如果您有任何其他内容,在论坛中告诉我