开发项目时,搜索往往是产品经理和leader很重视的功能,这也成了程序员很头疼的一块。我们开始思索,有没有哪个大神把搜索框架写好了,让我们直接使用呢,今天推荐一款来自twitter的搜索框架Typeahead.js。
***上fb,你会发现fb的搜索很具有代表性,敲入部分内容,会下拉出相关用户,群组,页面等等相关建议搜索信息。这篇文章就用Typeahead和php来仿造一个fb搜索功能。(Typeahead.js相关文档说明请到github查看)
Bloodhound是Typeahead.js中根据用户输入内容灵活给出相关搜索建议的引擎,不管是本地的还是来自远程服务器的内容,都能给予很好的展示。(下面所有代码我亲自测试过了,没有任何问题)
一,数据库设计
首先我们建立几个简单的表,字段如下图,需要注意的是,表使用MyISAM存储引擎,并且给name列设定FULLTEXT全文索引,其他没了。
数据表结构
二,视图界面
界面同样使用了来自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)
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官方文档进行自行编写。