65.9K
CodeProject 正在变化。 阅读更多。
Home

自动完成的轻量级实现

starIconstarIconstarIcon
emptyStarIcon
starIcon
emptyStarIcon

3.27/5 (5投票s)

2022年3月4日

CPOL

9分钟阅读

viewsIcon

6009

downloadIcon

83

自动完成(Autocomplete)或称单词补全,是一种应用程序预测用户正在输入的单词剩余部分的功能。本文描述了一种轻量级的实现方式。

1. 引言 目录

我最近访问了一个网站,其中有一个包含239个 `

<select name="adr_country" 
        id="adr_country" 
        tabindex="19" 
        class="DataFormDropDownList" 
        placeholder="Please select" 
        onchange="resetValidationState();
                  clearTimeout(timerid); 
                  timerid=setTimeout('__doPostBack(\'adr_country\',\'\')', 
                                     msecondsshort); " 
        onfocusin="expandDropDownList(this);" 
        onblur="collapseDropDownList(this);" 
        style="LEFT: 112px; 
               POSITION: absolute; 
               TOP: 259px; 
               HEIGHT: 19px; 
               WIDTH: 138px;">
  <option value="">Please select</option>
  <option value=""></option>
  <option value="AFGHANISTAN">AFGHANISTAN</option>
  <option value="Alaska">Alaska</option>
  <option value="ALBANIA">ALBANIA</option>
  <option value="ALGERIA">ALGERIA</option>
          ⋮ 231 additional <option>s
  <option value="YUGOSLAVIA">YUGOSLAVIA</option>
  <option value="ZAIRE">ZAIRE</option>
  <option value="ZAMBIA">ZAMBIA</option>
  <option value="ZIMBABWE">ZIMBABWE</option>
</select>

忽略绝对定位和不正确的 capitalization。重点是可供选择的项目数量相当可观。此外,在此表单中更新国家列表也很困难。

2. 需求 目录

对任何新的国家选择方法都应提出一些要求。

  • 不得使用任何第三方软件。这实际上排除了所有承诺“开箱即用”解决方案的软件(包括 Microsoft、jQuery、Node.js 等)。
  • 访客应看到一个易于操作且数量有限的选项供其选择。
  • 选项列表必须易于修改。
  • 实现应仅限于

一种名为自动完成的简单技术似乎满足了这些要求。

3. 概述 目录

自动完成(Autocomplete)或称单词补全,是一种应用程序预测用户正在输入的单词剩余部分的功能……自动完成通过在用户仅键入几个字符到文本输入字段后正确预测用户意图输入的单词,从而加快了人机交互的速度。它在可能的单词数量有限的领域中效果最好……
来自维基百科,自由的百科全书
 

4. 架构决策 目录

有两种方法可以创建自动完成功能

  • <select>
  • <datalist>

有多种方法可以填充 `` 元素的子元素出现。

  • 一个静态的 `
  • 一个静态的文本列表,以数组元素或对象成员的形式出现。必须定义一种方法,将文本转换为 `` 或 `` 元素的子元素 `
  • 不幸的是,选项1和2不提供自动完成功能,必须排除进一步考虑。

    选项3的困难在于,它的结果与原始(产生大量数据)相同。此外,必须创建模拟自动完成功能的方法。有关此类方法,请参阅如何 - 自动完成 [^]。

    选项4依赖于 MySQL 的模式匹配能力。虽然必须开发检索和格式化方法,但主要的计算由 MySQL 执行。这些计算经过高度优化。

    因此,此自动完成实现所采用的架构是选项4。

    Archecture

    5. 实现 目录

    随着 HTML Data List 元素 [^] 的引入,开发自动完成实现相对简单。此类实现的形​​式可以是

        <input id="country" 
               type="text" 
               list="country_list"
               autocomplete="off" 
               placeholder="Enter Country" />
        <datalist id="country_list"></datalist>

    现在的问题是如何为 datalist 提供选项?

    对于此项目,决定从 MySQL 存储过程返回的值构建 `

    5.1. MySQL 数据库 目录

    我的数据库编码指南规定,在代码表名称后附加后缀“_CT”。代码表包含两个字段:一个字段是值(通常太长而无法放入数据库),另一个字段包含第一个字段的代码(通常是一个与该值关联的单字符或双字符代码)。一个极端的例子是

                        country                          code
    
        South Georgia and the South Sandwich Islands      GS
    

    5.1.1.  目录

    此项目包含的数据库中有两个表值得关注。有两个表是因为不仅要收集国家信息,还要收集州信息。同一 HTML 页面上的两个自动完成字段也将用于测试是否可以由该实现服务于同一 HTML 页面上的两个字段。

    这些表是

        Countries_CT
            country
            code
    
        States_CT
            state
            code

    对于 Countries_CT,有一个 VARCHAR(64) 字段,包含完整的国家名称,还有一个 VARCHAR(2) 字段,包含国际两字符国家代码。对于 States_CT,有一个 VARCHAR(32) 字段,包含完整的美国州名称,还有一个 VARCHAR(2) 字段,包含 USPS 两字符州代码。

    这些表的源(作为逗号分隔值文件)包含在项目下载中,名为 **countries.csv** 和 **states.csv**。

    5.1.2. 存储过程 目录

    MySQL 在 SELECT 语句的 LIKE [^] 子句中提供了一种模式匹配形式(另一种形式是 RLIKE [^])。在 LIKE 形式中,字符 '%' 匹配任意数量的字符(包括零个字符)。在 MySQL 中,SQL 模式默认情况下不区分大小写。

    有两个存储过程值得关注

    get_partial_countries

        PROCEDURE `get_partial_countries`(IN `partial` VARCHAR(64))
          SELECT country 
          FROM `Countries_CT` 
          WHERE country LIKE CONCAT(partial,'%')
          ORDER BY country ASC

    get_partial_states

        PROCEDURE `get_partial_states`(IN `partial` VARCHAR(32))
          SELECT state 
          FROM `States_CT` 
          WHERE state LIKE CONCAT(partial,'%'); 
          ORDER BY state ASC

    两者非常相似,只是访问的表和检索的字段不同。

    这些存储过程的源包含在项目下载中,名为 **get_partial_countries.sql** 和 **get_partial_states.sql**。

    5.1.3. 示例 目录

    这些示例涉及从 Countries_CT 代码表中提取一个或多个国家名称。如果用户输入“m”,则存储过程

        SELECT country 
        FROM `Countries_CT` 
        WHERE country LIKE CONCAT("m",'%') 
        ORDER BY country ASC;

    将返回所有以“m”开头后跟任意字符的条目(请记住模式不区分大小写)

        Macao
        Macedonia, the former Yugoslav Republic of
        Madagascar
        Malawi
        Malaysia
        Maldives
        Mali
        Malta
        Marshall Islands
        Martinique
        Mauritania
        Mauritius
        Mayotte
        Mexico
        Micronesia, Federated States of
        Moldova, Republic of
        Monaco
        Mongolia
        Montenegro
        Montserrat
        Morocco
        Mozambique
        Myanmar

    如果用户添加字母“o”并输入“mo”,则存储过程

        SELECT country 
        FROM `Countries_CT` 
        WHERE country LIKE CONCAT("mo",'%') 
        ORDER BY country ASC;

    将返回所有以“mo”开头后跟任意字符的条目

        Moldova, Republic of
        Monaco
        Mongolia
        Montenegro
        Montserrat
        Morocco
        Mozambique

    最后,如果用户添加字母“r”并输入“mor”,则存储过程

        SELECT country 
        FROM `Countries_CT` 
        WHERE country LIKE CONCAT("mor",'%') 
        ORDER BY country ASC;

    将返回所有以“mor”开头后跟任意字符的条目

        Morocco

    在此过程中(m⇒mo⇒mor),用户随时可以看到所需的条目,只需突出显示该条目并按 Enter 键即可。因此,在第一个示例中,看到摩洛哥,用户可以突出显示该条目并按 Enter 键。

    5.2. PHP 接口 目录

    MySQL 存储过程不能直接从 JavaScript 执行。因此,PHP 被用来执行访问 MySQL 存储过程的中间功能。

    有两个存储过程,所以有两个 PHP 模块。

    partial_countries.php

        <?php // partial_countries.php
    
        ini_set ( "display_errors", 1 );
        error_reporting ( E_ALL );
                                                // define connection variables
        include 'config.php'; 
                                                // get partial prefix of field
        $partial = strval ( htmlspecialchars ( $_GET [ 'partial' ] ) );
                                                // open DB connection and 
                                                // handle possible DB error
        $link = mysqli_connect ( $servername, 
                                 $username, 
                                 $password, 
                                 $database );
        if ( mysqli_connect_errno ( ) ) 
          {
          printf ( "Connect failed: %s\r\n", mysqli_connect_error ( ) );
          exit(1);
          }
                                                // build and execute the SQL;
                                                // handle possible DB error
        $sql = "CALL get_partial_countries ('".$partial."')";
        $result = mysqli_query ( $link, $sql );
        if ( !$result )
          {
          mysqli_close ( $link );
          printf ( "Query failed: %s\n", mysqli_error ( $link ) );
          exit(1);
          } 
                                                // process associative array
        while ( $post = mysqli_fetch_assoc ( $result ) ) 
          {
                                                // output each row
          echo ",".$post [ 'country' ];
          }
    
        mysqli_free_result ( $result );
    
        mysqli_close ( $link );
    
        ?>

    partial_states.php

        <?php // partial_states.php
    
        ini_set ( "display_errors", 1 );
        error_reporting ( E_ALL );
                                                // define connection variables
        include 'config.php';
                                                // get partial prefix of field
        $partial = strval ( htmlspecialchars ( $_GET [ 'partial' ] ) );
                                                // open DB connection and 
                                                // handle possible DB error
        $link = mysqli_connect ( $servername, 
                                 $username, 
                                 $password, 
                                 $database );
        if ( mysqli_connect_errno ( ) ) 
          {
          printf ( "Connect failed: %s\r\n", mysqli_connect_error ( ) );
          exit(1);
          }
                                                // build and execute the SQL;
                                                // handle possible DB error
        $sql = "CALL get_partial_states ('".$partial."')";
        $result = mysqli_query ( $link, $sql );
        if ( !$result )
          {
          mysqli_close ( $link );
          printf ( "Query failed: %s\n", mysqli_error ( $link ) );
          exit(1);
          } 
                                                // process associative array
        while ( $post = mysqli_fetch_assoc ( $result ) ) 
          {
                                                // output each row
          echo ",".$post['state'];
          }
    
        mysqli_free_result ( $result );
    
        mysqli_close ( $link );
    
        ?>

    同样,两者非常相似,只是访问的存储过程和检索的字段不同。

    config.php 包含服务器名、用户名、密码和数据库的声明。其内容形式为

        <?php // config.php
    
        $servername = host name or an IP address;
        $username = MySQL user name;
        $password = user's password;
        $database = default database to be used when performing queries;
    
        ?>

    这些值用于连接 MySQL 服务器。

    在每个模块中,

    1. 定义错误报告
    2. 检索连接变量
    3. 从查询字符串检索用户输入
    4. 获取与数据库的连接
    5. 构造并提交 MySQL 语句
    6. 将返回值转换为关联数组
    7. 将数组中的值连接成一个用逗号分隔的字符串
    8. 释放不再需要的内存

    5.3. JavaScript 目录

    将调用者与 PHP 接口绑定的软件是一个 JavaScript 模块

        // *************************** AutoComplete **************************
    
        // AutoComplete - a module of utility functions that support 
        //                autocomplete operations 
        //
        // this module creates a single global symbol "AutoComplete" if it 
        // does not already exist
    
        var AutoComplete;
        if ( !AutoComplete )
          {
          AutoComplete = { };
          }
        else if ( typeof AutoComplete !== "object" )
          {
          throw new Error ( 
                        "AutoComplete already exists but is not an object" );
          }

    模块声明后跟一个匿名函数,其中包含三个 JavaScript 方法。第一个是第二个的初始化程序;第二个是事件处理程序。($ 辅助函数包含以示完整性。)

      // *************************************************************** $
    
      // local entry point
    
      function $ ( id )
        {
    
        return ( document.getElementById ( id ) );
    
        } // $
    
    
      // ****************************************************** initialize
      
      // global entry point
      
      /// synopsis:
      ///   AutoComplete.initialize ( input_id,
      ///                             PHP_filename );
      ///
      /// <summary>
      ///   initialize the autocomplete event handler for the input_id 
      ///   element
      ///
      /// <param name="input_id">
      ///   string containing the id of the text <input> element that has 
      ///   a datalist to be filled by autocomplete operations
      /// 
      /// <param name="PHP_filename">
      ///   string containing the name of the PHP file that will retrieve 
      ///   values to be placed in the datalist of the <input> element
      ///
      /// <remarks>
      ///   the result of this method is the attachment of the 
      ///   autocomplete keyup event handler to the specified text 
      ///   <input> element
    
      function initialize ( input_id,
                            PHP_filename )
        {
        var input_element = $ ( input_id );
                                            // add a keyup event listener 
                                            // to the input element
        input_element.addEventListener ( 
          "keyup", 
          function ( event )
            {
            keyup_handler ( event, PHP_filename );
            }
          );
    
        } // initialize

    对于之前的例子

        <input id="country" 
               type="text" 
               list="country_list"
               autocomplete="off" 
               placeholder="Enter Country" />
        <datalist id="country_list"></datalist>

    要使用 PHP 文件 `partial_countries.php` 为 `` 元素 `country` 初始化一个自动完成的 keyup 处理程序,将使用以下代码:

        <script src="auto_complete.js"></script>
    
        <script>
    
          AutoComplete.initialize ( "country",
                                    "partial_countries.php" );
                                    
        </script>

    实际的自动完成工作由 keyup 事件处理程序执行。请注意,每次输入一个字符时,keyup 事件处理程序都会触发。

      // *************************************************** keyup_handler
    
      // local entry point
    
      function keyup_handler ( event,
                               PHP_filename ) 
        {
        var input = event.target;
    
        if  ( !isNaN ( input.value ) || 
              ( input.value.length < MINIMUM_CHARACTERS ) ) 
          { 
          return;
          } 
        else if ( input.list === null )
          { 
          return;
          } 
        else 
          { 
          var datalist = input.list;
          var url = PHP_filename + "?partial=" + input.value;
          var xhr = new XMLHttpRequest ( );
    
          xhr.onreadystatechange = 
            function ( ) 
              {
              if ( ( this.readyState == 4 ) && ( this.status == 200 ) ) 
                {
                var items = this.responseText.split ( ',' );
    
                datalist.innerHTML = "";
    
                items.forEach ( 
                  function ( item ) 
                    {
                    if ( item.toLowerCase ( ).
                              startsWith ( input.value.toLowerCase ( ) ) )
                      { 
    	                                         // create a new <option>
                      var option = new Option ( item, item, false, false );
    
                      option.value = item;
                      datalist.appendChild ( option );
                      }
                    }
                  );
                }
              };
    
          xhr.open ( "GET", url, true );
          xhr.send ( );
          }
    
        } // keyup_handler

    关于 keyup_handler 的几点说明。

    • keyup_handler 由 initialize 函数声明为 keyup 事件处理程序。其目的是为 `` 元素 `list` 属性中定义的 `` 提供 `
    • 守卫
      if ( item.toLowerCase ( ).startsWith ( input.value.toLowerCase ( ) ) )

      执行两个功能

      • 确保返回的项是用户输入派生的值。
      • 确保不会将多余的字符添加到任何返回值中。特别是,字节顺序标记 [^],它可能包含在返回数组的第一个元素中。
         
    • XMLHttpRequest 调用是异步的。
    • 此实现可以由单个 HTML 页面上的多个 `` 元素调用。

    5.4. HTML 页面 目录

    自动完成实现完成后,它就取代了国家的原始 select 语句。我对结果感到惊喜。唯一需要的修改是

    • 实际用 `` 和 `` 元素替换 `` 元素的宽度值,因此 incorporated 了 width 属性以确保自动完成返回的值能够适应。

      6. 参考文献 目录

      7. 下载 目录

      文件下载(在引用的 ZIP 文件中)包含演示自动完成功能的文件。

          auto_complete.js
          config.php
          countries_CT.csv
          get_partial_countries.sql
          get_partial_states.sql
          partial_countries.php
          partial_states.php
          states_CT.csv
          test.html

      建议

      • 将所有下载的文件放在 PHP/MySQL 托管服务器上的单个目录中。
      • 将 CSV 文件导入到 Countries_CT 和 States_CT 表中,正如其名称所示。
      • 修改 config.php 文件的内容,以便在 PHP/MySQL 托管服务器上使用。
      • 使用这两个 SQL 文件在 PHP/MySQL 托管服务器上开发存储过程。

       

      8. 结论 目录

      本文提供了在不使用第三方软件的情况下,在 `` 元素上实现自动完成所需代码。

      9. 开发环境 目录

      Autocomplete 项目是在以下环境中开发的:

      Microsoft Windows 7 专业版 SP 1
      Microsoft Visual Studio 2008 专业版 SP1
      Microsoft Visual C# 2008
      Microsoft .NET Framework 版本 3.5 SP1

      10. 支持的浏览器 目录

      以下显示了支持自动完成功能的浏览器。

      Chrome Edge Firefox Firefox_Developer Internet_Explorer Opera Safari
      Chrome Edge Firefox Firefox
      开发版
      互联网
      Explorer
      Opera Safari

      Internet Explorer 和 Safari 都没有适用于 Windows 7 的版本。

      11. 历史 目录

      03/04/2022 原文
    © . All rights reserved.