分页数据搜索功能
在前面 无刷新分页 中,已经实现了简单的分页功能,这一篇我们将对其进行扩展,为其实现搜索功能。
首先预览下搜索将要实现的效果:
点击搜索列头,弹出搜索区域(包括排序、清除、搜索框),搜索框中输入搜索结果,确定后完成搜索,多列搜索将对搜索条件进行叠加处理。
下面来实现搜索功能,首先是为列头绑定事件,这里需要找出那些可用于搜索的列头,前面已经提到过搜索支持的4种类型,在搜索中需要为这些列头设置相应的属性,这里自定义queryType属性,比如:
<td>
<asp:Label runat="server" columnName="NAME" queryType="text">名称</asp:Label>
</td>
先不用关注上面使用的Lable服务器控件,这里暂时先只作为查询支撑控件。
接下来可以找出所有标有queryType属性的标签,这些标签的父控件即为可用于搜索的列头,为其绑定鼠标单击事件:
tableObj.find('[queryType]').each(function(n){
labelObj[n] = $(this);
parentObj = labelObj[n].parent();
parentObj.bind('click', function(){
getSearchArea(labelObj[n]);
});
});
//获取搜索区域
function getSearchArea(o){
}
getSearchArea方法里面,我们需要先创建搜索区域(一段标准HTML代码),设置其样式可浮动、绝对定位,创建后附加到页面中,并以列头作为参照物获取坐标并显示:
function setPosition(params){
//获取列头坐标
var tdObj = params.label.parent();
var left = tdObj.position().left + 5;
var top = tdObj.position().top + 20;
if(left + 200 > SCREEN_WIDTH){
left = SCREEN_WIDTH - 200;
}
//设置搜索区域坐标
searchMap.Item(params.containerId).Item('main').css({ left: left, top: top});
searchMap.Item(params.containerId).Item('main').show();
}
上面方法中的searchMap.Item(params.containerId).Item('main')为创建好的搜索主区域,先不关注它,后面再介绍。
然后获取所有搜索对象:升序、降序、清除、确定、取消,分别为其绑定相应的事件方法。
这样,搜索区域已经被创建好,点击列表头时将显示于表头下方,在本篇中,创建的搜索区域已经包含了所有搜索方式的4种输入框,并且为其定义唯一的编号,后面的操作只是根据搜索类型来显示或隐藏,大家可参照源文件进行对比,这里不进行描述。
接下来要思考的是如何获取搜索结果,从上面信息来看,每一次搜索都涉及到下面几个关键信息:
搜索字段、搜索类型、搜索条件、搜索内容。
因此,我们至少需要将这些关键信息传递给分页控件,最常用的方法的是将每类信息作为一个参数通过URL传参方式传递给分页控件,但这种方式会造成参数数量增多,而且扩展不灵活。
这里用另一种方式实现,我们定义一种数据协议,将获取到的每类信息按照“类型-值”的方式进行组合,最后将每一列的搜索结果作为一个整体,样式如下:
columnName┝COLON┥VAL┝SEPARATOR┥
queryType┝COLON┥text┝SEPARATOR┥
condition┝COLON┥contain┝SEPARATOR┥
value┝COLON┥10┝SEPARATOR┥
┝END┥
这里以┝END┥作为多列的分隔符,┝SEPARATOR┥作为每种信息的分隔符,┝COLON┥作为各具体信息描述及结果的分隔符。
格式定义好了,可以以此为目标进行搜索实现,每一次搜索的结果都需要进行保存,用于下一次搜索的条件叠加。
这里用一个字典来保存搜索结果:
var searchResult = new Map(); //搜索结果,格式为:字段、搜索信息集合(顺序为:搜索类型、搜索条件、值)
前面提到过获取搜索主区域:searchMap.Item(params.containerId).Item('main')的方法,这是另一个字典对象,保存类搜索信息区域,控制显示及隐藏,定义为:
var searchMap = new Map(); //页面控件对象,格式为:各列唯一编号、对象类型、对象
在每一次搜索完成时,根据获取到的列、类型、搜索结果,组合成前面定义的数据格式,提交给后台进行处理,并保存至searchResult中,下面是一些以文本搜索方式例的关键代码:
//确定按钮点击事件
function okButtonClick(params){
searchByText(params);
getSearchResult(params);
//提交搜索条件
pagerObj.Page(1);
searchMap.Item(params.containerId).Item('main').hide();
}
//获取文本搜索内容
function searchByText(params){
if(params.queryType != 'text'){
return;
}
createSearchResult(params);
searchResult.Item(params.columnName)[0] = params.queryType; //搜索类型
searchResult.Item(params.columnName)[1] = searchMap.Item(params.containerId).Item('textCondition').val(); //搜索条件
searchResult.Item(params.columnName)[2] = searchMap.Item(params.containerId).Item('textResult').val(); //搜索结果
}
//添加搜索结果,格式为:字段名、搜索类型、搜索条件、结果
function createSearchResult(params){
if(!searchResult.Exists(params.columnName)){
searchResult.Add(params.columnName, new Array());
}
}
//获取搜索结果值,组合成搜索结果格式
function getSearchResult(params){
var result = '';
var keys = searchResult.Keys();
for(var i = 0; i < keys.length; i++){
result += 'columnName┝COLON┥' + keys[i] + '┝SEPARATOR┥';
result += 'queryType┝COLON┥' + searchResult.Item(keys[i])[0] + '┝SEPARATOR┥'
result += 'condition┝COLON┥' + searchResult.Item(keys[i])[1] + '┝SEPARATOR┥'
result += 'value┝COLON┥'
for(var j = 2; j < searchResult.Item(keys[i]).length; j++){
if(j > 2){
result += '┝JOIN┥';
}
result += searchResult.Item(keys[i])[j] ;
}
result += '┝SEPARATOR┥'
result += '┝END┥';
}
$('#' + searchResultId).val(result);
}
至此,前台搜索处理已经结束。
接下来,在分页控件获取到提交的数据源后,对其进行数据解析,由于前面定义的数据协议在后台可以解析成键值对,我们可以对其键、值进行判断、处理,最终生成查询的WHERE条件,还是以文本搜索方式为例:
/// <summary>
/// 解析提交数据格式
/// </summary>
public static Dictionary<int, Dictionary<string, string>> AnalyseDataSource(string dataSource)
{
Dictionary<int, Dictionary<string, string>> dictSource = new Dictionary<int, Dictionary<string, string>>();
int index = 0;
string[] sltRows = Regex.Split(dataSource, "┝END┥");
foreach (string strRow in sltRows)
{
if (strRow.Length == 0)
{
continue;
}
string[] sltCols = Regex.Split(strRow, "┝SEPARATOR┥");
foreach (string strCol in sltCols)
{
if (strCol.Length == 0)
{
continue;
}
string[] sltVals = Regex.Split(strCol, "┝COLON┥");
if (sltVals.Length != 2)
{
continue;
}
if (!dictSource.ContainsKey(index))
{
dictSource.Add(index, new Dictionary<string, string>());
}
string col = sltVals[0];
if (dictSource[index].ContainsKey(col))
{
continue;
}
string val = sltVals[1];
dictSource[index].Add(col, val);
}
index++;
}
return dictSource;
}
/// <summary>
/// 解析分页搜索查询条件
/// </summary>
public string AnalysePagerWhere(string dataSource)
{
StringBuilder sb = new StringBuilder();
Dictionary<int, Dictionary<string, string>> dictSource = AnalyseDataSource(dataSource);
IDataAnalyse analyseObj = null;
foreach (int keyIndex in dictSource.Keys)
{
analyseObj = null;
switch (dictSource[keyIndex]["queryType"])
{
case "text":
analyseObj = new DataAnalyseText(dictSource[keyIndex]["columnName"], dictSource[keyIndex]["condition"], dictSource[keyIndex]["value"]);
break;
case "date":
analyseObj = new DataAnalyseDate(dictSource[keyIndex]["columnName"], dictSource[keyIndex]["value"]);
break;
case "numeric":
analyseObj = new DataAnalyseNumeric(dictSource[keyIndex]["columnName"], dictSource[keyIndex]["condition"], dictSource[keyIndex]["value"]);
break;
case "values":
analyseObj = new DataAnalyseValues(dictSource[keyIndex]["columnName"], dictSource[keyIndex]["value"]);
break;
}
if (analyseObj != null)
{
sb.Append(analyseObj.GetWhere());
}
}
return sb.ToString();
}
/// <summary>
/// 数据源解析接口
/// </summary>
public interface IDataAnalyse
{
string GetWhere();
}
/// <summary>
/// 解析字符查询
/// </summary>
public class DataAnalyseText : IDataAnalyse
{
private string columnName = "";
private string condition = "";
private string val = "";
public DataAnalyseText(string columnName, string condition, string val)
{
this.columnName = columnName;
this.condition = condition;
this.val = val;
}
public string GetWhere()
{
string whereFmt = "";
switch (condition)
{
case "contain":
whereFmt = " AND ({0} LIKE '%{1}%' {2})";
break;
case "equal":
whereFmt = " AND ({0} = '{1}' {2})";
break;
case "start":
whereFmt = " AND ({0} LIKE '{1}%' {2})";
break;
case "end":
whereFmt = " AND ({0} LIKE '%{1}' {2})";
break;
}
string orWhere = "";
if (val.Length == 0)
{
orWhere = string.Format(" OR {0} IS NULL", columnName);
}
return string.Format(whereFmt, columnName, val, orWhere);
}
}
解析完成后的数据,在分页控件中进行调用,我们增加一个分页控件PagerBase,继承自FMPager,用于实现获取解析完成后的数据,并供其它具体分页控件调用:
public class PagerBase : FMPager
{
protected string orderSource = "";
protected override void OnLoad(EventArgs e)
{
base.OnLoad(e);
//获取查询条件
string querySource = HTMLHelper.Form("querySource");
orderSource = HTMLHelper.Form("orderSource");
DataAnalyseUtil util = new DataAnalyseUtil();
WHERE = util.AnalysePagerWhere(querySource);
}
public override void DataBind()
{
base.DataBind();
}
public string WHERE
{
get
{
return where;
}
set
{
where = value;
}
}
private string where = "";
public string ORDER_BY
{
get
{
return orderBy;
}
set
{
orderBy = value;
}
}
private string orderBy = "";
}
改造上一篇中使用的分页控件,将它的父类从FMPager改成PagerBase,将基类获取到的查询条件及排序条件带入:
/// <summary>
/// 存储过程分页
/// </summary>
public class ProcPager : PagerBase
{
protected override void OnLoad(EventArgs e)
{
base.OnLoad(e);
FM.Business.ProcPager inf = new Business.ProcPager();
this.ORDER_BY = orderSource.Length > 0 ? orderSource : "NAME";
int recordCount = 0;
string columns = "ID, NAME, VAL, INT_VAL, MUTIL_VAL, DATE_VAL";
DataSet ds = inf.GetProcList("TEST", columns, WHERE, ORDER_BY, this.CurrentPageIndex, this.PageSize, ref recordCount);
this.RecordCount = recordCount;
this.DataSource = ds.Tables[0].DefaultView;
base.DataBind();
}
}
这样,分页控件已经实现了按前台提交过来的搜索条件对数据进行的过滤。
最后来改造控件输出及前台页面显示。
控件输出,在原有的基础上为搜索列头添加搜索属性:
<asp:Repeater ID="rpList" runat="server">
<HeaderTemplate>
<table id="tbList" class="list_table" border="0" cellspacing="0" cellpadding="0">
<tr>
<td class="title" style="width: 20%;">
<asp:Label runat="server" columnName="NAME" queryType="text">名称</asp:Label>
</td>
<td class="title" style="width: 20%;">
<asp:Label id="Label1" runat="server" columnName="VAL" queryType="text">值</asp:Label>
</td>
<td class="title" style="width: 20%;">
<asp:Label id="Label2" runat="server" columnName="INT_VAL" queryType="numeric">数值</asp:Label>
</td>
<td class="title" style="width: 20%;">
<asp:Label id="Label3" runat="server" columnName="MUTIL_VAL" queryType="values">多选值</asp:Label>
</td>
<td class="title" style="width: 20%;">
<asp:Label id="Label4" runat="server" columnName="DATE_VAL" queryType="date">日期</asp:Label>
</td>
</tr>
</HeaderTemplate>
<ItemTemplate>
<tr>
<td><%# Eval("NAME") %></td>
<td><%# Eval("VAL") %></td>
<td><%# Eval("INT_VAL")%></td>
<td><%# Eval("MUTIL_VAL")%></td>
<td><%# Eval("DATE_VAL")%></td>
</tr>
</ItemTemplate>
<FooterTemplate>
</table>
</FooterTemplate>
</asp:Repeater>
<ctrl:ProcPager ID="pagerTest" RepeaterId="rpList" PageSize="10" runat="server" />
前台调用,增加对搜索对象PagerSearch的引用,PagerSearch是我们封装好的对象,上面已经提到了其中的一些关键方法:
var pagerObj = null;
var searchObj = null;
function LoadInfo(){
var url = 'AjaxHandler/ProcPager.aspx';
searchObj = new PagerSearch({
ajaxHandler: 'WebServices/QueryHandler.asmx/GetTestValues'
});
pagerObj = new PagerObj({
containerId: 'divPager',
url: url,
searchObj: searchObj
});
pagerObj.Page(1);
}
到此为此,整个搜索功能已经全部实际,运行的搜索页面,点击每列的列头,就可以看到本篇前面的预览效果。