Friday, July 30, 2010

自动完成 Auto-Complete Field with jQuery

转:

  1. http://fromvega.com/wordpress/2007/05/05/auto-complete-field-with-jquery-json-php/
  2. http://zendgeek.blogspot.com/2009/07/zend-framework-and-dojo-auto-complete.html
  3. http://www.phpme.info/index.php/view_108_351.html

12.7.5  利用YUI库创建自动完成的文本输入框

http://book.51cto.com/art/201003/187298.htm

(the best one!)

Building AutoComplete Inputs with PHP, PEAR, Dojo and YUI

http://devzone.zend.com/article/4229


Fashion Statements


Coco Chanel, the famous French designer, once commented that fashion "is not something that exists in dresses only". In recent months, I've come to agree: ever since the big search engines added auto-suggest, hardly a week goes by without a client asking me to build similar autocomplete functionality into a Web form.


Fortunately, modern programming toolkits like Dojo provide ready-made widgets that have the necessary client-side functions for autocomplete. Add a little bit of server-side glue, in the form of a PHP script that talks to a database to generate valid suggestions, and enabling this functionality in a Web application now becomes a matter of hours, rather than days. In this article, I'll show you how to do this using three different libraries: PEAR HTML_QuickForm, YUI, and Dojo. Come on in, and find out more!

Back To Basics


Before we get started, it's important to state the assumptions this article makes. While most of the code in this tutorial involves the use of ready-made widgets, you'll still find it easier to navigate if you understand HTML, know the basics of JavaScript and DOM programming, and are familiar with retrieving and processing SQL result sets in PHP.


This article also assumes that you have an Apache/PHP/MySQL development environment already set up, and that you have downloaded and successfully installed the following packages:

  1. The Dojo/Dijit Toolkit, which is available from http://www.dojotoolkit.org/. This article uses Dojo v1.2.3.
  2. The Yahoo! User Interface (YUI) Library, which is available from http://developer.yahoo.com/yui/. This article uses YUI v2.6.0.
  3. The HTML_QuickForm package and all necessary dependencies, which is available from http://pear.php.net/package/HTML_QuickForm, and is currently maintained by Bertrand Mansion and Alexey Borzov. This article uses HTML_QuickForm v3.2.9

A quick word also about the MySQL database used in this article. The various examples that follow demonstrate autocomplete functionality in the context of a Web form that accepts book information, consisting of the book title and up to three author names. Based on the first few characters entered into the form, author names are dynamically suggested from a database table, which looks something like this:


 mysql> SELECT * FROM author LIMIT 0,10;
+----------+--------------------+
| AuthorID | AuthorName         |
+----------+--------------------+
|        1 | Stephen King       |
|        2 | Alfred Hitchcock   |
|        3 | Enid Blyton        |
|        4 | Ken Follett        |
|        5 | W. G. Moore        |
|        6 | Jeffrey Arthur     |
|        7 | John le Carre      |
|        8 | Scott Smith        |
|        9 | Alison Prince      |
|       10 | Arthur Conan Doyle |
+----------+--------------------+
10 rows in set (0.13 sec)

An SQL file containing example data for this table can be downloaded here.

Remote Control


Once all the pieces are in place, let's look at one approach to enabling autocomplete functionality: using YUI's AutoComplete widget. This widget, which is documented in detail at http://developer.yahoo.com/yui/autocomplete/, can be associated with any form input element and is a fully-skinnable, easy-to-configure component that can read suggestion data in a variety of formats.


Here's an example of a form that uses the AutoComplete widget:


 <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
<html>
  <head>
  <link rel="stylesheet" type="text/css" href="/yui/build/fonts/fonts-min.css" />
  <link rel="stylesheet" type="text/css" href="/yui/build/autocomplete/assets/skins/sam/autocomplete.css" />
  <script type="text/javascript" src="/yui/build/yahoo-dom-event/yahoo-dom-event.js"></script>
  <script type="text/javascript" src="/yui/build/connection/connection-min.js"></script>
  <script type="text/javascript" src="/yui/build/animation/animation-min.js"></script>  
  <script type="text/javascript" src="/yui/build/datasource/datasource-min.js"></script>
  <script type="text/javascript" src="/yui/build/autocomplete/autocomplete-min.js"></script>
  </head>
  <body class="yui-skin-sam">
    
    <h3>Add Title</h3>
    <form action="" method="post">
      Title:<br/>
      <input type="text" name="Title[TitleName]" style="width:200px"/>
      <p/>
      Author:<br/>
      <div style="width:200px">
        <input id="Author[AuthorName][0]" type="text" name="Author[AuthorName][0]"/>
        <div id="ac0"></div>
      </div>
      <p/><br/><p/>
      Author:<br/>
      <div style="width:200px">
        <input id="Author[AuthorName][1]" type="text" name="Author[AuthorName][1]"/>
        <div id="ac1"></div>
      </div>
      <p/><br/><p/>
      Author:<br/>
      <div style="width:200px">
        <input id="Author[AuthorName][2]" type="text" name="Author[AuthorName][2]"/>
        <div id="ac2"></div>
      </div>
      <p/><br/><p/>
      <input type="submit" name="submit" value="Save" />
    </form>    
    
    <script type="text/javascript">                
      YAHOO.example.autocomplete = function() {
        var oConfigs = {
            prehighlightClassName: "yui-ac-prehighlight",
            queryDelay: 0,
            minQueryLength: 0,
            animVert: .01,
        }
        
        // instantiate remote data source
        // code based on example at
        // http://developer.yahoo.com/yui/examples/autocomplete/ac_basic_xhr.html
        var oDS = new YAHOO.util.XHRDataSource("http://localhost/get_authors.php");
        oDS.responseType = YAHOO.util.XHRDataSource.TYPE_XML;
        oDS.responseSchema = {
           resultNode: 'author',
           fields: ['name']            
        };
        oDS.maxCacheEntries = 10;        
    
        // instantiate YUI autocomplete widgets
        var oAC0 = new YAHOO.widget.AutoComplete("Author[AuthorName][0]", "ac0", oDS, oConfigs);          
        var oAC1 = new YAHOO.widget.AutoComplete("Author[AuthorName][1]", "ac1", oDS, oConfigs);          
        var oAC2 = new YAHOO.widget.AutoComplete("Author[AuthorName][2]", "ac2", oDS, oConfigs);
        return {
            oDS: oDS,
            oAC0: oAC0,
            oAC1: oAC1,
            oAC2: oAC2
        };
      }();
     </script>
    
  </body>
</html>


As the code illustrates, this form contains three input fields, each of which is associated with an AutoComplete widget. As the user begins entering data into each of these fields, the AutoComplete widget will go to work generating suggestions that match the user's input. The source data for these suggestions is YUI's XHRDataSource widget, which is configured as below:


         // instantiate remote data source
        var oDS = new YAHOO.util.XHRDataSource("http://localhost/get_authors.php");
        oDS.responseType = YAHOO.util.XHRDataSource.TYPE_XML;
        oDS.responseSchema = {
           resultNode: 'author',
           fields: ['name']            
        };


This configuration tells the XHRDataSource widget that it should automatically poll the PHP script at 'http://localhost/get_authors.php' for suggestions, and warns it that the PHP script will express these suggestions in XML. The configuration also specifies the XML node and attribute names that contain the suggestion text.


At the other end of the connection, here's what the 'get_authors.php' script looks like:


 <?php
// begin XML output
$xmlStr = <<<XML
<?xml version='1.0' standalone='yes'?>
<authors>
XML;

// open database connection
$mysqli = new mysqli("localhost", "user", "pass", "library");      
if (mysqli_connect_errno()) {
    printf("Connect failed: %s
", mysqli_connect_error());
    exit();
}

// retrieve author list matching input
// add to XML document
$q = $mysqli->real_escape_string($_GET['query']);
$sql = "SELECT AuthorName FROM author WHERE AuthorName LIKE '" . $q . "%' ORDER by AuthorName";
if ($result = $mysqli->query($sql)) {
  while ($row = $result->fetch_row()) {
    $xmlStr .= '<author name="' . $row[0] . '"></author>';
  }
  $result->close();
}

// clean up
// output XML document
$mysqli->close();
$xmlStr .= '</authors>';
header("Content-Type: text/xml");
echo $xmlStr;
?>


There's nothing particularly complicated about this script. It receives the string fragment entered by the user in $_GET['query'], formulates an SQL query to generate author names matching this fragment, and outputs an XML document containing these author names. Here's an example of the XML it generates in response to a query for 'ste':


 <?xml version='1.0' standalone='yes'?>
<authors>
  <author name="Stephen Fry"></author>
  <author name="Stephen King"></author>
  <author name="Stephen Mogridge"></author>
</authors>


At the other end of the connection, this XML is parsed and decoded by YUI's XHRDataSource widget and presented to the user. Here's an example of what the output looks like:

Going Local


One important point to note about the previous example, is that it continually "polls" the server to refine the suggestion list, as the user is typing. This might not be ideal in all situations - for example, when you're on a slow network link, or if your server is already heavily loaded and you don't want to unnecessarily burden it with additional work.


In these situations, you can populate the AutoComplete widget from a local data source - an inline JavaScript array - instead of from a remote URL. In this case, the array is dynamically generated using PHP when the page is rendered and used as the base for all autocomplete suggestions. The PHP code used to generate and populate the JavaScript array is similar to that used in the previous example. Here's the revised form:


 <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
<html>
  <head>
  <link rel="stylesheet" type="text/css" href="/yui/build/fonts/fonts-min.css" />
  <link rel="stylesheet" type="text/css" href="/yui/build/autocomplete/assets/skins/sam/autocomplete.css" />
  <script type="text/javascript" src="/yui/build/yahoo-dom-event/yahoo-dom-event.js"></script>
  <script type="text/javascript" src="/yui/build/animation/animation-min.js"></script>  
  <script type="text/javascript" src="/yui/build/datasource/datasource-min.js"></script>
  <script type="text/javascript" src="/yui/build/autocomplete/autocomplete-min.js"></script>
  </head>
  <body class="yui-skin-sam">
    
    <h3>Add Title</h3>
    <form action="" method="post">
      Title:<br/>
      <div style="width:200px">
        <input type="text" name="Title[TitleName]"/>
      </div>
      <p/>
      Author:<br/>
      <div style="width:200px">
        <input id="Author[AuthorName][0]" type="text" name="Author[AuthorName][0]"/>
        <div id="ac0"></div>
      </div>
      <p/><br/><p/>
      Author:<br/>
      <div style="width:200px">
        <input id="Author[AuthorName][1]" type="text" name="Author[AuthorName][1]"/>
        <div id="ac1"></div>
      </div>
      <p/><br/><p/>
      Author:<br/>
      <div style="width:200px">
        <input id="Author[AuthorName][2]" type="text" name="Author[AuthorName][2]"/>
        <div id="ac2"></div>
      </div>
      <p/><br/><p/>
      <input type="submit" name="submit" value="Save" />
    </form>    
    
    <?php
    // open database connection
    $mysqli = new mysqli("localhost", "user", "pass", "library");      
    if (mysqli_connect_errno()) {
        printf("Connect failed: %s
", mysqli_connect_error());
        exit();
    }
    // retrieve author list
    $query = "SELECT AuthorName FROM author ORDER by AuthorName";
    $authors = array();
    if ($result = $mysqli->query($query)) {
      while ($row = $result->fetch_row()) {
        $authors[] = $row[0];
      }
      $result->close();
    }
    // clean up
    $mysqli->close();
    ?>      
  
    <script type="text/javascript">                
      // set data
      arrayAuthors = [
        "<?php echo implode('","', $authors); ?>"
      ];      
      
      YAHOO.example.autocomplete = function() {
        var oConfigs = {
            prehighlightClassName: "yui-ac-prehighlight",
            queryDelay: 0,
            minQueryLength: 0,
            animVert: .01,
        }
        
        // instantiate local data source
        // code based on example at
        // http://developer.yahoo.com/yui/examples/autocomplete/ac_basic_array.html
        var oDS = new YAHOO.util.LocalDataSource(arrayAuthors);
    
        // instantiate YUI autocomplete widgets
        var oAC0 = new YAHOO.widget.AutoComplete("Author[AuthorName][0]", "ac0", oDS, oConfigs);          
        var oAC1 = new YAHOO.widget.AutoComplete("Author[AuthorName][1]", "ac1", oDS, oConfigs);          
        var oAC2 = new YAHOO.widget.AutoComplete("Author[AuthorName][2]", "ac2", oDS, oConfigs);
        
        return {
            oDS: oDS,
            oAC0: oAC0,
            oAC1: oAC1,
            oAC2: oAC2
        };
      }();
     </script>
    
  </body>
</html>


You'll notice also that in this version, the autocomplete suggestions appear quicker, because the widget is operating off a local array rather than a remote request. However, this method is only advisable when the data set is relatively small, as otherwise the inline JavaScript array might grow too large and consume an uncomfortably high amount of client memory.

Typing Tutor


An alternative to the previous example is to use HTML_QuickForm's 'autocomplete' element, which also uses a local data cache and is fairly straightforward to implement. Here's an example:


 <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
<html>
  <head></head>
  <body>
    <h3>Add Title</h3>

    <?php
    // open database connection
    $mysqli = new mysqli("localhost", "user", "pass", "library");      
    if (mysqli_connect_errno()) {
        printf("Connect failed: %s
", mysqli_connect_error());
        exit();
    }
    // retrieve author list
    $query = "SELECT AuthorName FROM author ORDER by AuthorName";
    $authors = array();
    if ($result = $mysqli->query($query)) {
      while ($row = $result->fetch_row()) {
        $authors[] = $row[0];
      }
      $result->close();
    }
    // clean up
    $mysqli->close();
        
    // initialize HTML_QuickForm object
    require_once 'HTML/QuickForm.php';
    $form = new HTML_QuickForm();
    
    // add auto-complete input field
    $title = $form->addElement('text', 'Title[TitleName]', 'Title:');
    $ac0 = $form->addElement('autocomplete', 'Author[AuthorName][0]', 'Author:');
    $ac1 = $form->addElement('autocomplete', 'Author[AuthorName][1]', 'Author:');
    $ac2 = $form->addElement('autocomplete', 'Author[AuthorName][2]', 'Author:');
    
    // add auto-complete options
    $ac0->setOptions($authors);
    $ac1->setOptions($authors);
    $ac2->setOptions($authors);
    
    // add submit button
    $form->addElement('submit', null, 'Submit');
    
    // print submitted values
    // render and display the form
    if ($form->validate()) {
      $form->freeze();
    } else {
      $form->display();
    }
    ?>
  </body>
</html>


Here, an 'autocomplete' element is first added to the form with addElement(), and then its setOption() method is used to set the array of values that will be used for auto-completion. When the form is rendered, as the user types a name into the autocomplete field, the input entered will be matched against this array of values (on every key press) and matching values, if any, will be used to complete the user's input. Here's an illustration of what this looks like:



In this example, the array is generated by PHP from a MySQL result set, as in the earlier examples. The HTML_QuickForm package will use this array and automatically generate the JavaScript code needed to enable the autocompletion feature (look in the source code of the rendered page to see it).

Dijit-al Download


If you're a Dojo fan, then you'll be thrilled to hear that Dijit, the Dojo widget library, also comes with a ComboBox component that exposes autocomplete functionality. This ComboBox can be linked to an ItemFileReadStore, which provides suggestions using a JSON source (although other formats are also supported). Here's the code:


 <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
<html>
  <head>
    <style type="text/css">
      @import "/dojo-release-1.2.3/dijit/themes/tundra/tundra.css";
      @import "/dojo-release-1.2.3/dojo/resources/dojo.css"
    </style>  
    <script type="text/javascript" src="/dojo-release-1.2.3/dojo/dojo.js" djConfig="parseOnLoad: true"></script>
    <script type="text/javascript">
     dojo.require("dojo.parser");
     dojo.require("dijit.form.ComboBox");
     dojo.require("dojo.data.ItemFileReadStore");
    </script>
  </head>
  <body>
    <!--
    code based on example at
    http://dojotoolkit.org/book/dojo-book-0-9/part-2-dijit/form-validation-specialized-input/auto-completer
    -->
    <h3>Add Title</h3>
    <form action="" method="post">
      <div dojoType="dojo.data.ItemFileReadStore" jsId="aStore" url="http://localhost/get_authors_json.php" />
      Title:<br/>
      <div class="tundra" style="width:200px">    
        <input type="text" dojoType="dijit.form.TextBox" name="Title[TitleName]" />
      </div>
      <p/>
      Author:<br/>
      <div class="tundra" style="width:200px;">
        <input name="Author[AuthorName][0]" dojoType="dijit.form.ComboBox" store="aStore" searchAttr="name" />          
      </div>
      <p/>
      Author:<br/>
      <div class="tundra" style="width:200px">
        <input name="Author[AuthorName][1]" dojoType="dijit.form.ComboBox" store="aStore" searchAttr="name" />          
      </div>
      <p/>
      Author:<br/>
      <div class="tundra" style="width:200px">
        <input name="Author[AuthorName][2]" dojoType="dijit.form.ComboBox" store="aStore" searchAttr="name" />          
      </div>
      <p/>
      <input type="submit" name="submit" value="Save" />
    </form>    
    
  </body>
</html>


Notice, in the listing above, that each ComboBox element has a 'store' attribute, which points to the Dojo ItemFileReadStore. This ItemFileReadStore looks for JSON data to be in a particular format, which is documented at http://dojotoolkit.org/book/dojo-book-0-9/part-3-programmatic-dijit-and-dojo/what-dojo-data/available-stores/dojo-data-item. It's quite easy to generate this JSON data using a server-side PHP script, which is exactly what the script at 'http://localhost/get_authors_json.php' does:


 <?php
// open database connection
$mysqli = new mysqli("localhost", "user", "pass", "library");      
if (mysqli_connect_errno()) {
    printf("Connect failed: %s
", mysqli_connect_error());
    exit();
}
// retrieve author list matching input
// add to XML document
$sql = "SELECT AuthorName FROM author ORDER by AuthorName";
if ($result = $mysqli->query($sql)) {
  while ($row = $result->fetch_row()) {
    $authors[] = array('name' => $row[0]);
  }
  $result->close();
}

// clean up
// output JSON
$mysqli->close();
echo json_encode(array('identifier' => 'name', 'items' => $authors));
?>


Here's an example of the JSON output:


 {"identifier":"name",
"items":[
          {"name":"A. J. Quinnell"},
          {"name":"Agatha Christie"},
          ...
          {"name":"William McKay"},
          {"name":"William Rotsler"}
         ]

}


And here's what the final product looks like:



As these examples demonstrate, it's fairly easy to build an autocomplete input that uses a database for suggestions, by mixing a little PHP with ready-made widgets from toolkit such as Dojo or YUI. This article listed four possible ways in which this could be done...but there are many more! Play with it a little, discover your own...and don't forget to post a comment telling everyone about it!

0 评论:

Post a Comment