پیشنیاز این بحث مطالعهی مطالب «
صفحه بندی و مرتب سازی خودکار اطلاعات به کمک jqGrid در ASP.NET MVC» و «
فعال سازی و پردازش صفحات پویای افزودن، ویرایش و حذف رکوردهای jqGrid در ASP.NET MVC» است و در اینجا جهت کوتاه شدن بحث، صرفا به تغییرات مورد نیاز جهت اعمال بر روی مثالها اکتفاء خواهد شد.
صورت مساله public class Product
{
public int Id { set; get; }
public DateTime AddDate { set; get; }
public string Name { set; get; }
public decimal Price { set; get; }
}
در اینجا تعریف محصول، شامل خاصیتهای تاریخ ثبت، نام و قیمت آن است.
میخواهیم زمانیکه فرمهای پویای ویرایش یا افزودن رکوردها ظاهر شدند، در حین تکمیل نام، یک auto complete ظاهر شود:
در حین ورود تاریخ، یک date picker شمسی جهت سهولت ورود اطلاعات نمایش داده شود:
همچنین در قسمت ورود مبلغ و قیمت، به صورت خودکار حرف سه رقم جدا کننده هزارها، نمایش داده شوند تا کاربران در حین ورود مبالغ بالا دچار اشتباه نشوند.
پیشنیازها
- برای نمایش
auto complete از همان امکانات توکار jQuery UI که به همراه jqGrid عرضه میشوند، استفاده خواهیم کرد.
- برای نمایش date picker شمسی از مطلب «
PersianDatePicker یک DatePicker شمسی به زبان JavaScript که از تاریخ سرور استفاده میکند» کمک خواهیم گرفت.
- جهت اعمال خودکار حرف سه رقم جدا کننده هزارها از
افزونهی Price Format جیکوئری استفاده میکنیم.
تعریف و الحاق این پیشنیازها، فایل layout برنامه را به شکل زیر تغییر خواهد داد:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>@ViewBag.Title - My ASP.NET Application</title>
<link href="~/Content/themes/base/jquery.ui.all.css" rel="stylesheet" />
<link href="~/Content/jquery.jqGrid/ui.jqgrid.css" rel="stylesheet" />
<link href="~/Content/PersianDatePicker.css" rel="stylesheet" />
<link href="~/Content/Site.css" rel="stylesheet" type="text/css" />
</head>
<body>
<div>
@RenderBody()
</div>
<script src="~/Scripts/jquery-1.7.2.min.js"></script>
<script src="~/Scripts/jquery-ui-1.8.11.min.js"></script>
<script src="~/Scripts/i18n/grid.locale-fa.js"></script>
<script src="~/Scripts/jquery.jqGrid.min.js"></script>
<script src="~/Scripts/PersianDatePicker.js"></script>
<script src="~/Scripts/jquery.price_format.2.0.js"></script>
@RenderSection("Scripts", required: false)
</body>
</html>
تغییرات مورد نیاز سمت کلاینت، جهت اعمال افزونههای جیکوئری و سفارشی سازی عناصر دریافت اطلاعات
الف) نمایش auto complete در حین ورود نام محصولات colModel: [
{
name: 'Name', index: 'Name', align: 'right', width: 100,
editable: true, edittype: 'text',
editoptions: {
maxlength: 40,
dataInit: function (elem) {
// http://jqueryui.com/autocomplete/
$(elem).autocomplete({
source: '@Url.Action("GetProductNames","Home")',
minLength: 2,
select: function (event, ui) {
$(elem).val(ui.item.value);
$(elem).trigger('change');
}
});
}
},
editrules: {
required: true
}
}
],
برای اعمال هر نوع افزونهی جیکوئری به عناصر فرمهای خودکار ورود اطلاعات در jqGrid، تنها کافی است که رویداد dataInit یک ستون را بازنویسی کنیم. در اینجا توسط elem، المان جاری را در اختیار خواهیم داشت. سپس از این المان جهت اعمال افزونهای دلخواه استفاده میکنیم. برای مثال در اینجا از متد
autocomplete استفاده شدهاست که جزئی از jQuery UI استاندارد است.
برای پردازش سمت سرور آن و مقدار دهی url آن، یک چنین اکشن متدی را میتوان تدارک دید:
public ActionResult GetProductNames(string term)
{
var list = ProductDataSource.LatestProducts
.Where(x => x.Name.StartsWith(term))
.Select(x => x.Name)
.Take(10)
.ToArray();
return Json(list, JsonRequestBehavior.AllowGet);
}
مقدار term، عبارتی است که کاربر وارد کرده است. توسط متد StartsWith، کلیه نامهایی را که با این عبارت شروع میشوند (البته 10 مورد از آنها را) بازگشت میدهیم.
ب) نمایش date picker شمسی در حین ورود تاریخ colModel: [
{
name: 'AddDate', index: 'AddDate', align: 'center', width: 100,
editable: true, edittype: 'text',
editoptions: {
maxlength: 10,
// https://www.dntips.ir/post/1382
onclick: "PersianDatePicker.Show(this,'@today');"
},
editrules: {
required: true
}
}
],
Date picker
مورد استفاده، وابستگی خاصی به jQuery ندارد. مطابق مستندات آن باید در رویدادگردان onclick، این تقویم شمسی را فعال کرد. بنابراین در قسمت onclick دقیقا این مورد را اعمال میکنیم.
@{
ViewBag.Title = "Index";
var today = DateTime.Now.ToPersianDate();
}
مقدار today آن در ابتدای View به نحو فوق تعریف شدهاست. کدهای کامل متد کمکی ToPersianDate در پروژهی پیوست موجود است.
ج) اعمال حروف سه رقم جدا کننده هزارها در حین ورود قیمت colModel: [
{
name: 'Price', index: 'Price', align: 'center', width: 100,
formatter: 'currency',
formatoptions:
{
decimalSeparator: '.',
thousandsSeparator: ',',
decimalPlaces: 2,
prefix: '$'
},
editable: true, edittype: 'text',
editoptions: {
dir: 'ltr',
dataInit: function (elem) {
// http://jquerypriceformat.com/
$(elem).priceFormat({
prefix: '',
thousandsSeparator: ',',
clearPrefix: true,
centsSeparator: '',
centsLimit: 0
});
}
},
editrules: {
required: true,
minValue: 0
}
}
],
افزونهی
price format نیز یک افزونهی جیکوئری است. بنابراین دقیقا مانند حالت auto complete آنرا در dataInit فعال سازی میکنیم و همچنین یک سری تنظیم ابتدایی مانند مشخص سازی thousandsSeparator آنرا مقدار دهی خواهیم کرد.
یک نکته
همین تعاریف را دقیقا به فرمهای جستجو نیز میتوان اعمال کرد. در اینجا برای حالات ویرایش و افزودن رکوردها، editoptions مقدار دهی شدهاست؛ در مورد فرمهای جستجو باید searchoptions و برای مثال dataInit آنرا مقدار دهی کرد.
مشکل مهم!
با تنظیمات فوق، قسمت UI بدون مشکل کار میکند. اما اگر در سمت سرور، مقادیر دریافتی را بررسی کنیم، نه تاریخ و نه قیمت، قابل دریافت نیستند. زیرا تاریخ ارسالی به سرور شمسی است و مدل برنامه DateTime میلادی میباشد. همچنین به دلیل وجود حروف سه رقم جدا کننده هزارها، عبارت دریافتی قابل تبدیل به عدد نیستند و مقدار دریافتی صفر خواهد بود.
برای رفع این مشکلات، نیاز به تغییر model binder توکار ASP.NET MVC است. برای تاریخها از کلاس
PersianDateModelBinder میتوان استفاده کرد. برای اعداد decimal از کلاس ذیل:
using System;
using System.Globalization;
using System.Threading;
using System.Web.Mvc;
namespace jqGrid05.CustomModelBinders
{
/// <summary>
/// How to register it in the Application_Start method of Global.asax.cs
/// ModelBinders.Binders.Add(typeof(decimal), new DecimalBinder());
/// </summary>
public class DecimalBinder : DefaultModelBinder
{
public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
if (bindingContext.ModelType == typeof(decimal) || bindingContext.ModelType == typeof(decimal?))
{
return bindDecimal(bindingContext);
}
return base.BindModel(controllerContext, bindingContext);
}
private static object bindDecimal(ModelBindingContext bindingContext)
{
var valueProviderResult = bindingContext.ValueProvider.GetValue(bindingContext.ModelName);
if (valueProviderResult == null)
return null;
bindingContext.ModelState.SetModelValue(bindingContext.ModelName, valueProviderResult);
decimal value;
var valueAsString = valueProviderResult.AttemptedValue == null ?
null : valueProviderResult.AttemptedValue.Trim();
if (string.IsNullOrEmpty(valueAsString))
return null;
if (!decimal.TryParse(valueAsString, NumberStyles.Any, Thread.CurrentThread.CurrentCulture, out value))
{
const string error ="عدد وارد شده معتبر نیست";
var ex = new InvalidOperationException(error, new Exception(error, new FormatException(error)));
bindingContext.ModelState.AddModelError(bindingContext.ModelName, ex);
return null;
}
return value;
}
}
}
در اینجا عبارت ارسالی به سرور به صورت یک رشته دریافت شده و سپس تبدیل به یک عدد decaimal میشود. در آخر به سیستم model binding بازگشت داده خواهد شد. به این ترتیب دیگر مشکلی با پردازش حروف سه رقم جدا کننده هزارها نخواهد بود.
برای ثبت و معرفی این کلاسها باید به نحو ذیل در فایل global.asax.cs برنامه عمل کرد:
using System;
using System.Web.Mvc;
using System.Web.Routing;
using jqGrid05.CustomModelBinders;
namespace jqGrid05
{
public class MvcApplication : System.Web.HttpApplication
{
protected void Application_Start()
{
AreaRegistration.RegisterAllAreas();
RouteConfig.RegisterRoutes(RouteTable.Routes);
ModelBinders.Binders.Add(typeof(DateTime), new PersianDateModelBinder());
ModelBinders.Binders.Add(typeof(decimal), new DecimalBinder());
}
}
}
کدهای کامل این مثال را از اینجا میتوانید دریافت کنید jqGrid05.zip