使用Typeahead.js和PHP仿造facebook搜索框

使用Typeahead.js和PHP仿造facebook搜索框

开发项目时,搜索往往是产品经理和leader很重视的功能,这也成了程序员很头疼的一块。我们开始思索,有没有哪个大神把搜索框架写好了,让我们直接使用呢,今天推荐一款来自twitter的搜索框架Typeahead.js。


***上fb,你会发现fb的搜索很具有代表性,敲入部分内容,会下拉出相关用户,群组,页面等等相关建议搜索信息。这篇文章就用Typeahead和php来仿造一个fb搜索功能。(Typeahead.js相关文档说明请到github查看)


Bloodhound是Typeahead.js中根据用户输入内容灵活给出相关搜索建议的引擎,不管是本地的还是来自远程服务器的内容,都能给予很好的展示。(下面所有代码我亲自测试过了,没有任何问题)


一,数据库设计

首先我们建立几个简单的表,字段如下图,需要注意的是,表使用MyISAM存储引擎,并且给name列设定FULLTEXT全文索引,其他没了。

使用Typeahead.js和PHP仿造facebook搜索框
数据表结构


二,视图界面

界面同样使用了来自twitter的bootstrap(非常好用,phper必备)。

内容包含了简单的搜索框和一个用于展示返回的pre块。

<!DOCTYPE html>

<html>

  <head>

    <meta charset="utf-8">

    <meta http-equiv="X-UA-Compatible" content="IE=edge">

    <meta name="viewport" content="width=device-width, initial-scale=1">

    <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.min.css" integrity="sha384-1q8mTJOASx8j1Au+a5WDVnPi2lkFfwwEAa8hDDdjZlpLegxhjVME1fgjWPGmkzs7" crossorigin="anonymous">

    <link type="text/css" rel="stylesheet" href="css/style.css" />

    <!-- HTML5 shim and Respond.js for IE8 support of HTML5 elements and media queries -->

    <!--[if lt IE 9]>

      <script src="https://oss.maxcdn.com/html5shiv/3.7.2/html5shiv.min.js"></script>

      <script src="https://oss.maxcdn.com/respond/1.4.2/respond.min.js"></script>

    <![endif]-->

  </head>

  <body>

  

    <div class="container-fluid" style="background-color: #3A5795;">

      <div id="globalContainer" class="row">

        <div class="col-md-12">

          <div id="searchContainer">

            <form>

              <div class="form-group">

                <input type="text" class="form-control search-query" name="search" placeholder="Search friends, groups or pages" />

                <span class="search-icon glyphicon glyphicon-search"></span>

              </div>

            </form>

          </div>

        </div>

      </div>

    </div>

    

    <br/><br/>

    

    <div class="container">

      <div class="row">

        <div class="col-md-12">

          <pre id="responseDataContainer"></pre>

        </div>

      </div>

    </div>

    

    <!-- JS Libs - Load all scripts at the bottom -->

    <script type="text/javascript" src="js/jquery.min.js"></script>

    <script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/js/bootstrap.min.js" integrity="sha384-0mSbJDEHialfmuBBQP6A4Qrprq5OVfW37PRR3j5ELqxss1yVqOtnepnHVP9aJ7xS" crossorigin="anonymous"></script>

    <script type="text/javascript" src="js/typeahead.bundle.js"></script>

    <script type="text/javascript" src="js/search.js"></script>

  </body>

</html>


三、Ajax 脚本

既然是搜索,少不了ajax的应用

search.php

脚本根据接收到的搜索类型,执行不同的查询并返回结果。

<?php

// Connect to the database.

$mysql = new mysqli('db-host', 'db-user', 'db-pass', 'db-name') or die('could not connect to db');

$mysql->query('set names utf8’);//for chinese

if (!isset($_GET['type']) && empty($_GET['type'])) {

  echo json_encode(['error' => 'No type specified.']);

  exit;

}


$column = 'name';

$orderBy = '';


// Identify the correct table and column.

switch ($_GET['type']) {

  case 'friends':

    $table = 'friends';

    break;

  case 'groups':

    $table = 'groups';

    $orderBy = ' ORDER BY `members_count` DESC ';

    break;

  case 'pages':

    $table = 'pages';

    $orderBy = ' ORDER BY `likes` DESC ';

    break;

  default:

    $table = 'popular_search';

    $column = 'query';

    break;

}


$query = 'SELECT * FROM ' . $table .

  ' WHERE MATCH(`' . $column . '`) AGAINST("' . $_GET['query'] . '"IN BOOLEAN MODE) ' .

  $orderBy . ' LIMIT 5 ';


$result = $mysql->query($query);


if ($result && $result->num_rows > 0) {

  $resultset = array();

  while ($row = $result->fetch_assoc()) {

    $resultset[] = $row;

  }

  

  if ('search' === $_GET['type']) {

    $qObj = new stdClass();

    $qObj->id = 0;

    $qObj->query = 'search for ' . $_GET['query'];

    array_unshift($resultset, $qObj);

  }

  

  // Send response and return the data.

  echo json_encode($resultset);

  exit;

}


// If the type is search, return this response by default.

if ('search' === $_GET['type']) {

  $query = new stdClass();

  $query->id = 0;

  $query->total_search_count = 0;

  $query->query = 'search for ' . $_GET['query'];

  echo json_encode([$query]);

}


四、搜索系统(search.js)

使用Typeahead.js和PHP仿造facebook搜索框
Bloodhound

第一件事是定义各种搜索建议的数据集(Friends, Groups, Pages and Popular Search)

getBloodhoundSettings(type)方法将根据不同搜索类型,返回Bloodhound对象实例

/**

 * Bloodhound suggestion engine setting constructor.

 *

 * @param string type

 * @param object bloodhound construct setting

 */

function getBloodhoundSettings(type) {

  

  return {

    datumTokenizer: Bloodhound.tokenizers.whitespace,

    queryTokenizer: Bloodhound.tokenizers.whitespace,

    

    /**

     * Must return the identifier for the datum

     */

    identify: function(datum) {

      return datum.id;

    },

    

    /**

     * Fetch data from remote source using ajax

     */

    remote: {

      url: "ajax/search.php",

      

      /**

       * Prepare the settings for ajax request

       */

      prepare: function (query, settings) {

        settings.type = "GET";

        settings.contentType = "application/json; charset=UTF-8";

        settings.data = {

          'query' : query,

          'type' : type

        };  

        return settings;

      }

    }  

  }

}



然后创建不同Bloodhound实例如 Friends, Groups, Pages and Popular Search


// Bloodhound "search" suggestion dataset

var searchBHSettings = getBloodhoundSettings('search');

var search = new Bloodhound(searchBHSettings);

// Bloodhound "friends" suggestion dataset

var friendsBHSettings = getBloodhoundSettings('friends');

var friends = new Bloodhound(friendsBHSettings);

// Bloodhound "groups" suggestion dataset

var groupsBHSettings = getBloodhoundSettings('groups');

var groups = new Bloodhound(groupsBHSettings);

// Bloodhound "pages" suggestion dataset

var pagesBHSettings = getBloodhoundSettings('pages');

var pages = new Bloodhound(pagesBHSettings);


初始化Typeahead

一旦定义Bloodhound数据集,我们就根据后端返回提供相应的搜索建议


// Attach typeahead to the input

$('#searchContainer .search-query').typeahead({

  hint: true,

  highlight: true,

  minLength: 3

}, {

  name: 'search',

  source: search,

  templates: {

    header: '<h4 class="suggestion-header">Popular Searches</h4>',

    suggestion: function(datum) {

      if (datum) {

        return '<div id="popular-search-id-' + datum.id + '"><span><span class="popular-search-icon glyphicon glyphicon-search"></span> ' +

          datum.query + ' · <span class="meta-info">' + number_format(datum.total_search_count) + ' people talking about this</span></span></div>'; 

      }      

    }

  },

  display: function(suggestion) {

    // set the datum "identifier" that is selected or load data based on it.

    return suggestion.query;

  }

}, {

  name: 'search-friends',

  source: friends,

  templates: {

    header: '<h4 class="suggestion-header">Friends</h4>',

    suggestion: function(datum) {

      console.log('Freinds suggestion');

      console.log(datum);

      var img = '';

      if (datum.image) {

        img += '<img class="meta-img" src=storage/frds/' + datum.image + '></img>'; 

      }

      

      return '<div id="friend-search-id-' + datum.id + '"><span>' + img + datum.name +

        '<br/><span class="meta-info">' + datum.location + ' · ' + datum.occupation + '</span></span></div>';

    }

  },

  display: function(suggestion) {

    // set the datum "identifier" that is selected or load data based on it.

    return suggestion.name;

  }

}, {

  name: 'search-groups',

  source: groups,

  templates: {

    header: '<h4 class="suggestion-header">Groups</h4>',

    suggestion: function(datum) {

      var img = '';

      if (datum.image) {

        img += '<img class="meta-img" src=storage/grps/' + datum.image + '></img>'; 

      }

      

      return '<div id="friend-search-id-' + datum.id + '"><span>' + img + datum.name +

        '<br/><span class="meta-info">' + datum.type + ' · ' + number_format(datum.members_count) + ' members</span></span></div>';

    }

  },

  display: function(suggestion) {

    // set the datum "identifier" that is selected or load data based on it.

    return suggestion.name;

  }

}, {

  name: 'search-pages',

  source: pages,

  templates: {

    header: '<h4 class="suggestion-header">Pages</h4>',

    suggestion: function(datum) {

      var img = '';

      if (datum.image) {

        img += '<img class="meta-img" src=storage/pgs/' + datum.image + '></img>'; 

      }

      

      return '<div id="friend-search-id-' + datum.id + '"><span>' + img + datum.name +

        '<br/><span class="meta-info">' + datum.type + ' · ' + number_format(datum.likes) + ' like this</span></span></div>';

    }

  },

  display: function(suggestion) {

    // set the datum "identifier" that is selected or load data based on it.

    return suggestion.name;

  }

}).bind('typeahead:select', function(event, suggestion) {

  document.getElementById('responseDataContainer').innerHTML = JSON.stringify(suggestion);

});


/**

 * Number format function.

 *

 * https://github.com/kvz/phpjs/blob/master/functions/strings/number_format.js

 */



样式 - style.css

我们改写了一些typeahead默认样式,并且新定义了一些class

/**

 * Author: Tamil selvan K

 */


#globalContainer {

  margin: 5px auto 0px auto;

  max-width: 900px;

}


.twitter-typeahead {

  width: 100%;

}


/** typeahead override styles */


.tt-menu {

  width: 100%;

  border: 1px solid lightgray;

  border-radius: 4px;

  background-color: white;

}


div.tt-dataset .tt-suggestion:last-child {

  border-bottom: 0px !important;

}


.tt-suggestion {

  padding: 3px;

  margin: 2px;

  border-bottom: 1px solid lightgray;

}


.tt-suggestion:hover {

  cursor: pointer;

}


.tt-dataset {

  border-bottom: 4px solid #f6f7f8;

}


/** Custom modifications and styles */


.selection-header {

  border-radius: 4px;

}


.selection-footer {

  border-radius: 4px;

}


.suggestion-header {

  color: #b6b6b6;

  font-size: 15px;

  font-weight: 300;

  padding: 2px 5px;

}


.meta-info {

  font-size: 13px;

  color: #b6b6b7;

}


.meta-img {

  width: 36px;

  height: 36px;

  float: left;

  margin-right: 8px;

}


.search-icon {

  top: -25px;

  float: right;

  padding: 0px 10px;

}


.popular-search-icon {

  background-color: #4F85E8;

  border-radius: 25px;

  border-style: solid;

  border-width: 1px;

  padding: 8px;

  color: white;

  margin-right: 5px;

}


这样,所有代码已经书写完毕,赶紧执行以下看看效果吧。

如果你想做出更炫更牛x的,可以参考typeahead.js官方文档进行自行编写。


上一篇:React


下一篇:两次刷新世界纪录,这个中国程序员有点牛!