۱۳۸۹/۱۱/۲۳

استفاده از افزونه‌ي jQuery Autocomplete در ASP.NET


با استفاده از AutoComplete TextBoxes مي‌توان گوشه‌اي از زندگي روزمره‌ي كاربران يك برنامه را ساده‌تر كرد. مشكل مهم dropDownList ها دريك برنامه‌ي وب، عدم امكان تايپ قسمتي از متن مورد نظر و سپس نمايان شدن آيتم‌هاي متناظر با آن در اسرع وقت مي‌باشد. همچنين با تعداد بالاي آيتم‌ها هم حجم صفحه و زمان بارگذاري را افزايش مي‌دهند. راه حل‌هاي بسيار زيادي براي حل اين مشكل وجود دارند و يكي از آن‌ها ايجاد AutoComplete TextBoxes است. پلاگين‌هاي متعددي هم جهت پياده سازي اين قابليت نوشته‌ شده‌اند منجمله jQuery Autocomplete . اين پلاگين ديگر توسط نويسنده‌ي اصلي آن نگهداري نمي‌شود اما توسط برنامه نويسي ديگر در github ادامه يافته است. در ادامه نحوه‌ي استفاده از اين افزونه‌ را در ASP.NET Webforms بررسي خواهيم كرد.

الف) دريافت افزونه

لطفا به آدرس GitHub ذكر شده مراجعه نمائيد.

سپس براي مثال پوشه‌ي js را به پروژه افزوده و فايل‌هاي jquery-1.5.min.js ، jquery.autocomplete.js ، jquery.autocomplete.css و indicator.gif را در آن كپي كنيد. فايل indicator.gif به همراه مجموعه‌ي دريافتي ارائه نمي‌شود و يك آيكن loading معروف مي‌تواند باشد.
علاوه بر آن يك فايل جديد custom.js را نيز جهت تعاريف سفارشي خودمان اضافه خواهيم كرد.


ب) افزودن تعاريف افزونه به صفحه

در ذيل نحوه‌ي افزودن فايل‌هاي فوق به يك master page نمايش داده شده است.
در اينجا از قابليت‌هاي جديد ScriptManager (موجود در سرويس پك يك دات نت سه و نيم و يا دات نت چهار) جهت يكي كردن اسكريپت‌ها كمك گرفته شده است. به اين صورت تعداد رفت و برگشت‌ها به سرور به‌جاي سه مورد (تعداد فايل‌هاي اسكريپت مورد استفاده)، يك مورد (نهايي يكي شده) خواهد بود و همچنين حاصل نهايي به صورت خودكار به شكلي فشرده شده به مرورگر تحويل داده شده، سرآيندهاي كش شدن اطلاعات به آن اضافه مي‌گردد (كه در ساير حالات متداول اينگونه نيست)؛ به علاوه Url نهايي آن هم بر اساس hash فايل‌ها توليد مي‌شود. يعني اگر محتواي يكي از اين فايل‌ها تغيير كرد، چون Url نهايي تغيير مي‌كند، ديگر لازم نيست نگران كش شدن و به روز نشدن اسكريپت‌ها در سمت كاربر باشيم.

<%@ Master Language="C#" AutoEventWireup="true" CodeBehind="Site.master.cs" Inherits="AspNetjQueryAutocompleteTest.Site" %>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
<title></title>
<asp:PlaceHolder Runat="server">
<link href="<%= ResolveClientUrl("~/js/jquery.autocomplete.css")%>" rel="stylesheet" type="text/css" />
</asp:PlaceHolder>
<asp:ContentPlaceHolder ID="head" runat="server">
</asp:ContentPlaceHolder>
</head>
<body>
<form id="form1" runat="server">
<asp:ScriptManager ID="ScriptManager1" runat="server">
<CompositeScript>
<Scripts>
<asp:ScriptReference Path="~/js/jquery-1.5.min.js" />
<asp:ScriptReference Path="~/js/jquery.autocomplete.js" />
<asp:ScriptReference Path="~/js/custom.js" />
</Scripts>
</CompositeScript>
</asp:ScriptManager>
<div>
<asp:ContentPlaceHolder ID="ContentPlaceHolder1" runat="server">
</asp:ContentPlaceHolder>
</div>
</form>
</body>
</html>
علت استفاده از ResolveClientUrl در حين تعريف فايل css در اينجا به عدم مجاز بودن استفاده از ~ جهت مسير دهي فايل‌هاي css در header صفحه بر مي‌گردد.


ج) افزودن يك صفحه‌ي ساده به برنامه
<%@ Page Title="" Language="C#" MasterPageFile="~/Site.Master" AutoEventWireup="true"
CodeBehind="default.aspx.cs" Inherits="AspNetjQueryAutocompleteTest._default" %>

<asp:Content ID="Content1" ContentPlaceHolderID="head" runat="server">
</asp:Content>
<asp:Content ID="Content2" ContentPlaceHolderID="ContentPlaceHolder1" runat="server">
<asp:TextBox ID="txtShenas" runat="server" />
</asp:Content>

فرض كنيد مي‌خواهيم افزونه‌ي ذكر شده را به TextBox استاندارد فوق اعمال كنيم. ID اين TextBox در نهايت به شكل ContentPlaceHolder1_txtShenas رندر خواهد شد. البته در ASP.NET 4.0 با تنظيم ClientIDMode=Static مي‌توان ID انتخابي خود را به جاي اين ID خودكار درنظر گرفت و اعمال كرد. اهميت اين مساله در قسمت (ه) مشخص مي‌گردد.


د) فراهم آوردن اطلاعات مورد استفاده توسط افزونه‌ي AutoComplete به صورت پويا

مهم‌ترين قسمت استفاده از اين افزونه، تهيه‌ي اطلاعاتي است كه بايد نمايش دهد. اين اطلاعات بايد به صورت فايلي كه هر سطر آن حاوي يكي از آيتم‌هاي مورد نظر است، تهيه گردد. براي اين منظور مي‌توان از فايل‌هاي ASHX يا همان Generic handlers استفاده كرد:

using System;
using System.Data.SqlClient;
using System.Text;
using System.Web;

namespace AspNetjQueryAutocompleteTest
{
public class AutoComplete : IHttpHandler
{
public void ProcessRequest(HttpContext context)
{
string prefixText = context.Request.QueryString["q"];
var sb = new StringBuilder();

using (var conn = new SqlConnection())
{
//todo: اين مورد بايد از فايل كانفيگ خوانده شود
conn.ConnectionString = "Data Source=(local);Initial Catalog=MyDB;Integrated Security = true";
using (var cmd = new SqlCommand())
{
cmd.CommandText = @" select Field1 ,Field2 from tblData where Field1 like @SearchText + '%' ";
cmd.Parameters.AddWithValue("@SearchText", prefixText);
cmd.Connection = conn;
conn.Open();
using (var sdr = cmd.ExecuteReader())
{
if (sdr != null)
while (sdr.Read())
{
string field1 = sdr.GetValue(0) == DBNull.Value ? string.Empty : sdr.GetValue(0).ToString().Trim();
string field2 = sdr.GetValue(1) == DBNull.Value ? string.Empty : sdr.GetValue(1).ToString().Trim();
sb.AppendLine(field1 + "|" + field2);
}
}
}
}

context.Response.Write(sb.ToString());
}

public bool IsReusable
{
get
{
return false;
}
}
}
}

در اين مثال از ADO.NET كلاسيك استفاده شده است تا به عمد نحوه‌ي تعريف پارامترها يكبار ديگر مرور گردند. اگر از LINQ to SQL يا Entity framework يا NHibernate و موارد مشابه استفاده مي‌كنيد، جاي نگراني نيست؛ زيرا كوئري‌هاي SQL توليدي توسط اين ORMs به صورت پيش فرض از نوع پارامتري هستند (+).
در اين مثال اطلاعات دو فيلد يك و دوي فرضي از جدولي با توجه به استفاده از like تعريف شده دريافت مي‌گردد. به عبارتي همان متد StartsWith معروف LINQ بكارگرفته شده است.
به صورت خلاصه افزونه، كوئري استرينگ q را به اين فايل ashx ارسال مي‌كند. سپس كليه آيتم‌هاي شروع شده با مقدار دريافتي، از بانك اطلاعاتي دريافت شده و هر كدام قرارگرفته در يك سطر جديد بازگشت داده مي‌شوند.
اگر دقت كرده باشيد در قسمت sb.AppendLine ، با استفاده از "|" دو مقدار دريافتي از هم جدا شده‌اند. عموما يك مقدار كفايت مي‌كند (در 98 درصد موارد) ولي اگر نياز بود تا توضيحاتي نيز نمايش داده شود از اين روش نيز مي‌توان استفاده كرد. براي مثال يك مقدار خاص به همراه توضيحات آن به عنوان يك آيتم نمايش داده شده مد نظر است.


ه) اعمال نهايي افزونه به TextBox

در ادامه پياده سازي فايل custom.js براي استفاده از امكانات فراهم شده در قسمت‌هاي قبل ارائه گرديده است:

function formatItem(row) {
return row[0] + "<br/><span style='text-align:justify;' dir='rtl'>" + row[1] + "</span>";
}

$(document).ready(function () {
$("#ContentPlaceHolder1_txtShenas").autocomplete('AutoComplete.ashx', {
//Minimum number of characters a user has to type before the autocompleter activates
minChars: 0,
delay: 5,
//Only suggested values are valid
mustMatch: true,
//The number of items in the select box
max: 20,
//Fill the input while still selecting a value
autoFill: false,
//The comparison doesn't looks inside
matchContains: false,
formatItem: formatItem
});
});

پس از اين مقدمات، اعمال افزونه‌ي autocomplete به textBox ايي با id مساوي ContentPlaceHolder1_txtShenas ساده است. اطلاعات از فايل AutoComplete.ashx دريافت مي‌گردد و تعدادي از خواص پيش فرض اين افزونه در اينجا مقدار دهي شده‌اند. ليست كامل آن‌ها را در فايل jquery.autocomplete.js مي‌توان مشاهده كرد.
تنها نكته‌ي مهم آن استفاده از پارامتر اختياري formatItem است. اگر در حين تهيه‌ي AutoComplete.ashx خود تنها يك آيتم را در هر سطر نمايش مي‌دهيد و از "|" استفاده نكرده‌ايد، نيازي به ذكر آن نيست. در اين مثال ويژه، فيلد يك در يك سطر و فيلد دو در سطر دوم يك آيتم نمايش داده مي‌شوند: