مطالب
ایجاد یک HtmlHelper سفارشی با پشتیبانی از UnobtrusiveValidationAttributes
همانطور که می‌دانید، در MVC برای اعتبارسنجی داده‌ها در سمت کلاینت از کتابخانه‌ی jquery استفاده می‌شود. مایکروسافت از طریق jquery.validate.unobtrusive و گسترش کتابخانه‌ی jquery.validate توانسته منطق خود را برای اعتبارسنجی داده‌ها در سمت کلاینت پیاده سازی کند. 
برای این منظور MVC به کنترلهایی که باید اعتبارسنجی شوند، خصوصیاتی را از طریق Data Attribute اضافه می‌کند. برای مثال اگر در مدل خود فیلد ایمیل را به شکل زیر امضاء کرده باشید:
[Display(Name = "رایانامه")]
[Required(AllowEmptyStrings = false, ErrorMessage = "رایانامه خود را وارد کنید.")]
[RegularExpression("\\w+([-+.']\\w+)*@\\w+([-.]\\w+)*\\.\\w+([-.]\\w+)*", ErrorMessage = "نشانی رایانامه پذیرفتنی نمی‌باشد.")]
[ExistField(Action = "EmailExist", Namespace = "Parsnet.Controllers", Controller = "Account", ErrorMessage = "این رایانامه پیشتر به کار گرفته شده است.")]
        public string Email { get; set; }
و در View مورد نظر از Htmlhlper مربوطه به شکل زیر استفاده کرده باشید:
@Html.TextBoxFor(m => m.Email, new { @class = "form-control en", placeholder = @Html.DisplayNameFor(m => m.Email) })
در نهایت، Html خروجی در سمت کلاینت به شکل زیر خواهد بود:
<input data-val="true" data-val-existfiledvalidator="این رایانامه پیشتر به کار گرفته شده است." data-val-existfiledvalidator-url="/account/emailexist" data-val-regex="نشانی رایانامه پذیرفتنی نمی‌باشد." data-val-regex-pattern="\w+([-+.']\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*" data-val-required="رایانامه خود را وارد کنید." id="Email" name="Email" placeholder="رایانامه" value="" type="text">
و در اینجا کتابخانه‌ی اعتبارسنجی MVC با استفاده از همین خصوصیات *-data، اطلاعات مورد نیاز را جهت نمایش، اعتبارسنجی، تنظیم و بکارگیری، مورد استفاده قرار می‌دهد.
در یکی از پروژه‌هایی که در حال کار کردن بر روی آن هستم لازم شد تا این اطلاعات اعتبارسنجی به یک تگ span اعمال شوند. سناریوی مورد نظر به این صورت است که در بخش پروفایل کاربر، کاربر می‌تواند اطلاعات خود را بصورت inline ویرایش کنید. برای اینکار از کتابخانه X-editable استفاده کردم که از این لینک قابل دریافت است.
ابتدا اطلاعات موردنیاز در یک تگ span نمایش داده می‌شوند و در ادامه کاربر پس از کلیک بر روی آیکن ویرایش، امکان تغییر آن فیلد را دارد. برای اعتبارسنجی داده‌ها لازم بود تا تمامی اطلاعات مورد نیاز اعتبارسنجی در سمت کلاینت را به شکلی در اختیار داشته باشم و به ذهنم رسید تا با ایجاد یک Helper سفارشی، خصوصیات موردنظر را به تگ span اعمال کنم و سپس در سمت کلاینت از آن استفاده کنم. در واقع با اینکار با استفاده از همان کلاس مدل و این Helper سفارشی، از وارد کردن دستی داده‌ها و خصوصیات اجتناب کنم. (تصور کنید چیزی حدود 30 فیلد که هرکدام حداقل 4 خصوصیت دارند)
با نگاهی به سورس MVC دیدم پیاده سازی این قابلیت چندان سخت نیست و به راحتی با ایجاد یک Helper سفارشی، منطق خود را پیاده سازی و اعتبارسنجی در سمت کلاینت را به راحتی اعمال کردم.
برای ایجاد این Helper سفارشی ابتدا یک کلاس استاتیک ایجاد کنید و با استفاده از extension Method‌ها یک helper جدید را ایجاد کنید:
namespace Parsnet
{
     public static MvcHtmlString SpanFor<TModel, TProperty>(this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, TProperty>> expression, object htmlAttributes)
        {
            
            var sb = new StringBuilder();

            var span = new TagBuilder("span");

            var metadata = ModelMetadata.FromLambdaExpression<TModel, TProperty>(expression, htmlHelper.ViewData);
            var name = ExpressionHelper.GetExpressionText(expression);
            var fullName = htmlHelper.ViewContext.ViewData.TemplateInfo.GetFullHtmlFieldName(name);
            var value = "";

            if (metadata.Model != null && metadata.Model.GetType() == typeof(List<IdentityProvider.IdentityRole>))
            {
                var modelList = (List<IdentityProvider.IdentityRole>)metadata.Model;
                value = String.Join("، ", modelList.Select(r => r.Name));
            }
            else
            {
                value = htmlHelper.FormatValue(metadata.Model, null);
            }

            span.MergeAttributes<string, object>(((IDictionary<string, object>)HtmlHelper.AnonymousObjectToHtmlAttributes(htmlAttributes)));

            var fieldName = fullName.Split('.')[1];
            span.MergeAttribute("data-name", fieldName, true);
            span.MergeAttributes<string, object>(htmlHelper.GetUnobtrusiveValidationAttributes(name, metadata));

            sb.Append(span.ToString(TagRenderMode.StartTag));
            sb.Append(value);
            sb.Append(span.ToString(TagRenderMode.EndTag));

            return new MvcHtmlString(sb.ToString());
        }
    }
}
ما در این helper سفارشی از عبارت‌های لامبدا استفاده می‌کنیم و با استفاده از این عبارات، فیلد مورد نظر مدل خود را به helper معرفی می‌کنیم. آرگومان htmlAttributes در متد helper نیز برای دریافت خصوصیات اضافی helper است؛ خصوصیاتی مانند class، id, style و غیره.
با استفاده از کلاس TagBuilder تگ مورد نظر خود را ایجاد می‌کنیم. در اینجا من تگ span را ایجاد کرده‌ام که شما می‌توانید هر تگ دلخواه دیگری را نیز ایجاد کنید. اولین مرحله، استخراج اطلاعات موردنیاز از metadata مدل است که در خط زیر با پردازش عبارت لامبدا اینکار صورت می‌گیرد:
var metadata = ModelMetadata.FromLambdaExpression<TModel, TProperty>(expression, htmlHelper.ViewData);
سپس نام فیلد مورد نظر را از مدل استخراج می‌کنیم:
var name = ExpressionHelper.GetExpressionText(expression);
var fullName = htmlHelper.ViewContext.ViewData.TemplateInfo.GetFullHtmlFieldName(name);
کدهای فوق نام فیلد جاری (در اینجا Email) را از MetaData برای ما استخراج می‌کند. متغیر value برای نگهداری مقدار این فیلد از مدل است. مرحله بعد استخراج مقدار فیلد و انتساب آن به متغیر value است.
در سناریوی من کاربر می‌تواند زمینه‌ی فعالیت خود را انتخاب کند که به صورت IdentityRole پیاده سازی شده است. من در اینجا چک می‌کنیم که اگر نوع داده‌ای این فیلد List<IdentityProvider.IdentityRole> بود زمینه فعالیت کاربر را از طریق "،" از هم جدا کرده و به صورت یک رشته تبدیل می‌کنم. در غیر اینصورت همان مقدار عادی فیلد را بکار می‌گیرم.
if (metadata.Model != null && metadata.Model.GetType() == typeof(List<IdentityProvider.IdentityRole>))
            {
                var modelList = (List<IdentityProvider.IdentityRole>)metadata.Model;
                value = String.Join("، ", modelList.Select(r => r.Name));
            }
            else
            {
                value = htmlHelper.FormatValue(metadata.Model, null);
            }
سپس خصوصیات سفارشی خود را که بصورت attribute‌های HTML هستند، در خط زیر به تگ سفارشی اعمال می‌شوند:
span.MergeAttributes<string, object>(((IDictionary<string, object>)HtmlHelper.AnonymousObjectToHtmlAttributes(htmlAttributes)));
مهمترین مرحله که در واقع هدف اصلی من بود، استخراج خصوصیت‌های *-data برای اعتبارسجی است که در خط زیر اینکار صورت گرفته است:
 span.MergeAttributes<string, object>(htmlHelper.GetUnobtrusiveValidationAttributes(name, metadata));
نحوه‌ی استفاده از این helper سفارشی هم خیلی ساده است:
@Html.SpanFor(m => m.Profile.Email, new { @class = "editor", data_type = "text" })
و در نهایت HTML خروجی به شکل زیر است:
<span class="editor" data-name="Email" data-type="text" data-val="true" data-val-existfiledvalidator="این رایانامه پیشتر به کار گرفته شده است." data-val-existfiledvalidator-url="/account/emailexist" data-val-regex="نشانی رایانامه پذیرفتنی نمی‌باشد." data-val-regex-pattern="\w+([-+.']\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*" data-val-required="رایانامه خود را وارد کنید.">alireza_s_84@yahoo.com</span>
دیدن شکل‌های زیر خالی از لطف نیستند:

و پس از ویرایش:


البته برای درک بهتر این موضوع سعی خواهم کرد تا با یک مثال عملی کامل، نحوه‌ی پیاده سازی را در همینجا قرار دهم.
مطالب
تخته وایت برد آنلاین توسط SignalR
همانطور که در دوره SignalR سایت نیز مطرح شده‌است : "یکی از کاربردهای جالب SignalR می‌تواند به روز رسانی مداوم صفحه نمایش کاربران، توسط اطلاعات ارسالی از طرف سرور باشد." در ادامه میخواهیم به طراحی یک "تخته وایت برد" آنلاین بپردازیم.
در این پروژه برای ترسم خطوط بر روی صفحه از Canvas در  HTML5 استفاده میشود.

پیشنیازها :
پیشنیازهای این مطلب با مطلب «مثال - نمایش درصد پیشرفت عملیات توسط SignalR» یکی است. برای مثال، نحوه دریافت وابستگی‌ها، تنظیمات فایل global.asax و افزودن اسکریپت‌ها، تفاوتی با مقاله‌ی ذکر شده ندارد و تنها تعدادی اسکریپت و CSS جهت زیبایی کار افزوده شده است :
<link href="Styles/bootstrap.min.css" rel="stylesheet" />

//برای نمایش و استفاده از جعبه‌ی رنگ در بوت استراپ
<link href="Styles/bootstrap-colorpalette.css" rel="stylesheet" />
<script src="Scripts/jquery-1.8.2.js"></script>
<script type="text/javascript" src='Scripts/jquery.signalR-1.1.3.js'></script>
<script src="Scripts/bootstrap.min.js"></script>
<script src="Scripts/bootstrap-colorpalette.js"></script>
<script type="text/javascript" src='<%: ResolveClientUrl("~/signalr/hubs") %>'></script>

//حاوی متدهایی برای رسم خط با استفاده از HTML5
<script src="Scripts/draw.js" type="text/javascript"></script>

//تعریف کلاینت‌های متصل به هاب
<script src="Scripts/Whiteboard.js"></script>

  تعریف کلاس Hub برنامه :

using Microsoft.AspNet.SignalR;
using Microsoft.AspNet.SignalR.Hubs;
using OnlineSignalRWhiteboard.Model;

namespace OnlineSignalRWhiteboard.Hubs
{
    [HubName("onWhiteboard")]
    public class Whiteboard : Hub
    {
        public void OnDrawPen(Point prev, Point current, string color, int width)
        {
            Clients.All.drawPen(prev, current, color, width);
        }

        public void ClearBoard()
        {
            Clients.All.clear();
        }

    }
}
در ابتدا توسط ویژگی HubName یک نام مشخص برای هاب خود انتخاب کرده ایم. سپس متدی به نام OnDrawPen تعریف شده است که پس از فراخوانی از سمت کلاینت، مقادیر دریافتی خود را با صدا زدن متدی در سمت کلاینت به نام drawPen  ، به تمام کلاینت‌ها ارسال و نقاط مربوطه در سمت کلاینت، بر روی صفحه رسم میشوند.
متد دیگری به نام ClearBoard نیز تعریف شده است که روال رخدادگردان clear در سمت کلاینت را فراخوانی میکند و باعث پاک شدن صفحه نمایش میشود.

کدهای کلاینت‌های متصل به هاب در فایل Whiteboard.js 
 :
$(function () {
    $.connection.hub.logging = true;
    var whiteboard = $.connection.onWhiteboard;

    $("#clear").click(function () {
        //پاک کردن صفحه نمایش
        whiteboard.server.clearBoard();
    });

    var color = function (colors) {
        //تغییر رنگ قلم
        draw.colour = colors;
    };

    $(".size").click(function () {
        //تغییر سایز قلم
        draw.lineWidth = $(this).height();
        $('#size').css('height', $(this).height());
    });

    $.connection.hub.start().done(function () {
    })
    .fail(function () {
        alert("Could not Connect!");
     });

    draw.onDraw = function (prev, current, color, width) {
        //ارسال پارامترها به سمت سرور تا سرور بتواند داده‌ها را به سمت سایر کلاینت‌ها نیز ارسال کند
        whiteboard.server.onDrawPen(prev, current, color, width);
    };

    whiteboard.client.drawPen = function (prev, current, color, width) {
        draw.drawPen(prev, current, color, width);
    };

    whiteboard.client.clear = function () {
        draw.clear();
    };

    $('#colorpalette3').colorPalette()
      .on('selectColor', function (e) {
          $('#color').css('background-color', e.color);
          color(e.color);
      });

    var canvas = document.getElementById('draw');
    //تغییر سایر کانواس متناسب با پنجره‌ی مرورگر
    window.addEventListener('resize', resizeCanvas, false);

    function resizeCanvas() {
        canvas.width = window.innerWidth - 20;
        canvas.height = window.innerHeight - 70;
    }
    resizeCanvas();

});
در این قطعه کد ابتدا ارجاعی به هاب مشخص شده است و سپس روال‌های رخداد گردانی مانند whiteboard.client.drawPen و whiteboard.client.clear تعریف شده اند که به ترتیب متصل هستند به فراخوانی های Clients.All.drawPen و  Clients.All.clear در سمت سرور.
همچنین یک متد به نام draw.onDraw تعریف شده است که وظیفه‌ی آن ارسال اطاعاتی نظیر موقعیت موس در صفحه، رنگ، اندازه و ...  به سمت متدی به نام onDrawPen در هاب است.

کدهای کامل سمت کلاینت :
<!DOCTYPE html>

<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
    <title></title>
    <link href="Styles/bootstrap.min.css" rel="stylesheet" />
    <link href="Styles/bootstrap-colorpalette.css" rel="stylesheet" />
    <script src="Scripts/jquery-1.8.2.js"></script>
    <script type="text/javascript" src='Scripts/jquery.signalR-1.1.3.js'></script>
    <script src="Scripts/bootstrap.min.js"></script>
    <script src="Scripts/bootstrap-colorpalette.js"></script>
    <script type="text/javascript" src='<%: ResolveClientUrl("~/signalr/hubs") %>'></script>
    <script src="Scripts/draw.js" type="text/javascript"></script>
    <script src="Scripts/Whiteboard.js"></script>
</head>
<body>
    <form id="form1" runat="server">
        <div>
            <div style="position: static;">
              <div>
                <div>
                  <a data-toggle="collapse" data-target=".navbar-inverse-collapse">
                    <span></span>
                    <span></span>
                    <span></span>
                  </a>
                  <a href="#">تخته وایت برد آنلاین توسط SignalR و HTML5</a>
                  <div>
                    <ul>
                      <li>
                          <div>
                              <a id="selected-color2" data-toggle="dropdown">
                                  <div style="width: 20px; height: 20px; background: black" id="color">
                                  </div>
                              </a>
                              <ul style="width:293px;">
                                <li style="display:inline-block;">
                                  <div>رنگ پس زمینه</div>
                                  <div id="colorpalette3"></div>
                                </li>
                              </ul>
                          </div>
                      </li>
                      <li></li>
                      <li>
                          <div>
                              <a data-toggle="dropdown" style="width: 120px; height: 20px" href="#">
                                <hr style="width: 120px; height: 1px; background-color: black;margin: 0px; display: table-cell" id="size">
                              </a>
                              <ul style="width: 120px">
                                <li><hr style="width: 140px; height: 1px; background-color: black"></li>
                                  <li></li>
                                <li><hr style="width: 140px; height: 3px; background-color: black"></li>
                                  <li></li>
                                <li><hr style="width: 140px; height: 7px; background-color: black"></li>
                                  <li></li>
                                <li><hr style="width: 140px; height: 10px; background-color: black"></li>
                                  <li></li>
                                <li><hr style="width: 140px; height: 20px; background-color: black"></li>
                              </ul>
                            </div>
                      </li>
                      <li></li>
                      <li>
                          <div>
                            <a href="#" id="clear"><i></i>جدید</a>
                          </div>
                      </li>
                    </ul>
                  </div><!-- /.nav-collapse -->
                </div>
              </div><!-- /navbar-inner -->
            </div>
        </div>
        
        <div>
            <div>
                 <canvas id="draw" style="cursor: crosshair;border: 1px solid black;"></canvas>
            </div>
       </div>
    </form>
</body>
</html>
در ابتدا یک دکمه برای تغییر رنگ قلم و یک لیست بازشو برای تعیین اندازه‌ی قلم و همچنین یک دکمه برای پاک کردن صفحه نمایش قرار داده شده است. سپس یک Canvas به نام draw ایجاد کرده ایم که بتوانیم خطوط خود را بر روی آن رسم کنیم.

مشاهده نسخه نمایشی 
کدهای کامل این مثال را میتوانید از اینجا دانلود کنید : OnlineSignalRWhiteboard.zip  
و همچنین میتوانید نمونه‌ی بهینه شده‌ی آن را نیز از این قسمت دانلود کنید :  OnlineCustomSignalRWhiteboard.zip  
مطالب
تغییر استراتژی ساخت مدل در EF5 و رفع مشکل WCF Ria
Entity framework 5 نسبت به نسخه‌های پیشین شاهد تغییرات بسیاری بوده است و مانند هر تغییر دیگری اینجا نیز ممکن است تغییرات ؛ باعث بروز مشکلاتی در روند توسعه نرم افزار شوند. EF در نسخه جدید خود در کدهای پشت صحنه Model به جای ObjectContext از DbContext که نسخه محدود شده ObjectContext می‌باشد استفاده می‌کند. همین امر به خودی خود باعث محدود شدن متدهای شئی Context شده است. متدها و خواصی که گاها برای انجام اعمال خاصی به آنها نیاز پیدا می‌کنیم ولی دیگر در دسترس نیستند. برای مثال برای یک برنامه خاص می‌خواستم مقدار CommandTimeout   را به صورت دستی برای شئی Context تنظیم کنم ؛ ولی کد زیر دیگر قابل استفاده نبود:
  context.CommandTimeout = 180;
همچنین این استفاده از DbContext در هنگام استفاده از WCF Ria در سیلورلایت باعث بروز مشکل شده و کلاس‌های مدل در هنگام تعریف Domain Service Class توسط WCF Ria قابل شناسایی نیستند.یعنی WCF RIA به صورت خودکار قادر به تشخیص کلاس‌های Model نمی‌باشد.
 

برای رفع این مشکل مراحل زیر را انجام دهید:

  1. دو فایل tt موجود در مدل را حذف نمایید.

 2. مدل را در حالت Designer باز کنید و در بخش خصوصیات مدل مقدار Code Generation Strategy را از None  به Default تغییر دهید.

3. پروژه را Rebuild نمایید. مشکل به همین سادگی رفع می‌شود.
حالا با خیال راحت می‌توانید کلاس‌های مدل را در پنجره Add New Domain Service Class مشاهده نمایید.
 

مطالب
Kendo UI MVVM
پیشنیازها
- «استفاده از Kendo UI templates »
- «اعتبار سنجی ورودی‌های کاربر در Kendo UI»
- «فعال سازی عملیات CRUD در Kendo UI Grid» جهت آشنایی با نحوه‌ی تعریف DataSource ایی که می‌تواند اطلاعات را ثبت، حذف و یا ویرایش کند.


در این مطلب قصد داریم به یک چنین صفحه‌ای برسیم که در آن در ابتدای نمایش، لیست ثبت نام‌های موجود، از سرور دریافت و توسط یک Kendo UI template نمایش داده می‌شود. سپس امکان ویرایش و حذف هر ردیف، وجود خواهد داشت، به همراه امکان افزودن ردیف‌های جدید. در این بین مدیریت نمایش لیست ثبت نام‌ها توسط امکانات binding توکار فریم ورک MVVM مخصوص Kendo UI صورت خواهد گرفت. همچنین کلیه اعمال مرتبط با هر ردیف نیز توسط data binding دو طرفه مدیریت خواهد شد.



Kendo UI MVVM

الگوی MVVM یا Model-View-ViewModel که برای اولین بار جهت کاربردهای WPF و Silverlight معرفی شد، برای ساده سازی اتصال تغییرات کنترل‌های برنامه به خواص ViewModel یک View کاربرد دارد. برای مثال با تغییر عنصر انتخابی یک DropDownList در یک View، بلافاصله خاصیت متصل به آن که در ViewModel برنامه تعریف شده‌است، مقدار دهی و به روز خواهد شد. هدف نهایی آن نیز جدا سازی منطق کدهای UI، از کدهای جاوا اسکریپتی سمت کاربر است. برای این منظور کتابخانه‌هایی مانند Knockout.js به صورت اختصاصی برای این کار تهیه شده‌اند؛ اما Kendo UI نیز جهت یکپارچگی هرچه تمامتر اجزای آن، دارای یک فریم ورک MVVM توکار نیز می‌باشد. طراحی آن نیز بسیار شبیه به Knockout.js است؛ اما با سازگاری 100 درصد با کل مجموعه.
پیاده سازی الگوی MVVM از 4 قسمت تشکیل می‌شود:
- Model که بیانگر خواص متناظر با اشیاء رابط کاربری است.
- View همان رابط کاربری است که به کاربر نمایش داده می‌شود.
- ViewModel واسطی است بین Model و View. کار آن انتقال داده‌ها و رویدادها از View به مدل است و در حالت binding دوطرفه، عکس آن نیز صحیح می‌باشد.
- Declarative data binding جهت رهایی برنامه نویس‌ها از نوشتن کدهای هماهنگ سازی اطلاعات المان‌های View و خواص ViewModel کاربرد دارد.

در ادامه این اجزا را با پیاده سازی مثالی که در ابتدای بحث مطرح شد، دنبال می‌کنیم.


تعریف Model و ViewModel

در سمت سرور، مدل ثبت نام برنامه چنین شکلی را دارد:
namespace KendoUI07.Models
{
    public class Registration
    {
        public int Id { set; get; }
        public string UserName { set; get; }
        public string CourseName { set; get; }
        public int Credit { set; get; }
        public string Email { set; get; }
        public string Tel { set; get; }
    }
}
در سمت کاربر، این مدل را به نحو ذیل می‌توان تعریف کرد:
    <script type="text/javascript">
        $(function () {
            var model = kendo.data.Model.define({
                id: "Id",
                fields: {
                    Id: { type: 'number' }, // leave this set to 0 or undefined, so Kendo knows it is new.
                    UserName: { type: 'string' },
                    CourseName: { type: 'string' },
                    Credit: { type: 'number' },
                    Email: { type: 'string' },
                    Tel: { type: 'string' }
                }
            });
        });
    </script>
و ViewModel برنامه در ساده‌ترین شکل آن اکنون چنین تعریفی را خواهد یافت:
    <script type="text/javascript">
        $(function () {
            var viewModel = kendo.observable({
                accepted: false,
                course: new model()
            });
        });
    </script>
یک viewModel در Kendo UI به صورت یک observable object تعریف می‌شود که می‌تواند دارای تعدادی خاصیت و متد دلخواه باشد. هر خاصیت آن به یک عنصر HTML متصل خواهد شد. در اینجا این اتصال دو طرفه است؛ به این معنا که تغییرات UI به خواص viewModel و برعکس منتقل و منعکس می‌شوند.


اتصال ViewModel به View برنامه

تعریف فرم ثبت نام را در اینجا ملاحظه می‌کنید. فیلدهای مختلف آن بر اساس نکات اعتبارسنجی HTML 5 با ویژگی‌های خاص آن، مزین شده‌اند. جزئیات آن‌را در مطلب «اعتبار سنجی ورودی‌های کاربر در Kendo UI» پیشتر بررسی کرده‌ایم.
اگر به تعریف هر فیلد دقت کنید، ویژگی data-bind جدیدی را هم ملاحظه خواهید کرد:
    <div id="coursesSection" class="k-rtl k-header">
        <div class="box-col">
            <form id="myForm" data-role="validator" novalidate="novalidate">
                <h3>ثبت نام</h3>
                <ul>
                    <li>
                        <label for="Id">Id</label>
                        <span id="Id" data-bind="text:course.Id"></span>
                    </li>
                    <li>
                        <label for="UserName">نام</label>
                        <input type="text" id="UserName" name="UserName" class="k-textbox"
                               data-bind="value:course.UserName"
                               required />
                    </li>
                    <li>
                        <label for="CourseName">دوره</label>
                        <input type="text" dir="ltr" id="CourseName" name="CourseName" required
                               data-bind="value:course.CourseName" />
                        <span class="k-invalid-msg" data-for="CourseName"></span>
                    </li>
                    <li>
                        <label for="Credit">مبلغ پرداختی</label>
                        <input id="Credit" name="Credit" type="number" min="1000" max="6000"
                               required data-max-msg="عددی بین 1000 و 6000" dir="ltr"
                               data-bind="value:course.Credit"
                               class="k-textbox k-input" />
                        <span class="k-invalid-msg" data-for="Credit"></span>
                    </li>
                    <li>
                        <label for="Email">پست الکترونیک</label>
                        <input type="email" id="Email" dir="ltr" name="Email"
                               data-bind="value:course.Email"
                               required class="k-textbox" />
                    </li>
                    <li>
                        <label for="Tel">تلفن</label>
                        <input type="tel" id="Tel" name="Tel" dir="ltr" pattern="\d{8}"
                               required class="k-textbox"
                               data-bind="value:course.Tel"
                               data-pattern-msg="8 رقم" />
                    </li>
                    <li>
                        <input type="checkbox" name="Accept"
                               data-bind="checked:accepted"
                               required />
                        شرایط دوره را قبول دارم.
                        <span class="k-invalid-msg" data-for="Accept"></span>
                    </li>
                    <li>
                        <button class="k-button"
                                data-bind="enabled: accepted, click: doSave"
                                type="submit">
                            ارسال
                        </button>
                        <button class="k-button" data-bind="click: resetModel">از نو</button>
                    </li>
                </ul>
                <span id="doneMsg"></span>
            </form>
        </div>
برای اتصال ViewModel تعریف شده به ناحیه‌ی مشخص شده با DIV ایی با Id مساوی coursesSection، می‌توان از متد kendo.bind استفاده کرد.
    <script type="text/javascript">
        $(function () {
            var model = kendo.data.Model.define({
            // ...
            });

            var viewModel = kendo.observable({
            // ...
            });

            kendo.bind($("#coursesSection"), viewModel);
        });
    </script>
به این ترتیب Kendo UI به بر اساس تعریف data-bind یک فیلد، برای مثال تغییرات خواص course.UserName را به text box نام کاربر منتقل می‌کند و همچنین اگر کاربر اطلاعاتی را در این text box وارد کند، بلافاصله این تغییرات در خاصیت course.UserName منعکس خواهند شد.
<input type="text" id="UserName" name="UserName" class="k-textbox"
       data-bind="value:course.UserName"
       required />

بنابراین تا اینجا به صورت خلاصه، مدلی را توسط متد kendo.data.Model.define، معادل مدل سمت سرور خود ایجاد کردیم. سپس وهله‌ای از این مدل را به صورت یک خاصیت جدید دلخواهی در ViewModel تعریف شده توسط متد kendo.observable در معرض دید View برنامه قرار دادیم. در ادامه اتصال ViewModel و View، با فراخوانی متد kendo.bind انجام شد. اکنون برای دریافت تغییرات کنترل‌های برنامه، تنها کافی است ویژگی‌های data-bind ایی را به آن‌ها اضافه کنیم.
در ناحیه‌ی تعریف شده توسط متد kendo.bind، کلیه خواص ViewModel در دسترس هستند. برای مثال اگر به تعریف ViewModel دقت کنید، یک خاصیت دیگر به نام accepted با مقدار false نیز در آن تعریف شده‌است (این خاصیت چون صرفا کاربرد UI داشت، در model برنامه قرار نگرفت). از آن برای اتصال checkbox تعریف شده، به button ارسال اطلاعات، استفاده کرده‌ایم:
<input type="checkbox" name="Accept"
       data-bind="checked:accepted"
       required />

<button class="k-button"
        data-bind="enabled: accepted, click: doSave"
        type="submit">
       ارسال
</button>
برای مثال اگر کاربر این checkbox را انتخاب کند، مقدار خاصیت accepted، مساوی true خواهد شد. تغییر مقدار این خاصیت، توسط ViewModel بلافاصله در کل ناحیه coursesSection منتشر می‌شود. به همین جهت ویژگی enabled: accepted که به معنای مقید بودن فعال یا غیرفعال بودن دکمه بر اساس مقدار خاصیت accepted است، دکمه را فعال می‌کند، یا برعکس و برای انجام این عملیات نیازی نیست کدنویسی خاصی را انجام داد. در اینجا بین checkbox و button یک سیم کشی برقرار است.


ارسال داده‌های تغییر کرده‌ی ViewModel به سرور

تا اینجا 4 جزء اصلی الگوی MVVM که در ابتدای بحث عنوان شد، تکمیل شده‌اند. مدل اطلاعات فرم تعریف گردید. ViewModel ایی که این خواص را به المان‌های فرم متصل می‌کند نیز در ادامه اضافه شده‌است. توسط ویژگی‌های data-bind کار Declarative data binding انجام می‌شود.
در ادامه نیاز است تغییرات ViewModel را به سرور، جهت ثبت، به روز رسانی و حذف نهایی منتقل کرد.
    <script type="text/javascript">
        $(function () {
            var model = kendo.data.Model.define({
                //...
            });

            var dataSource = new kendo.data.DataSource({
                type: 'json',
                transport: {
                    read: {
                        url: "api/registrations",
                        dataType: "json",
                        contentType: 'application/json; charset=utf-8',
                        type: 'GET'
                    },
                    create: {
                        url: "api/registrations",
                        contentType: 'application/json; charset=utf-8',
                        type: "POST"
                    },
                    update: {
                        url: function (course) {
                            return "api/registrations/" + course.Id;
                        },
                        contentType: 'application/json; charset=utf-8',
                        type: "PUT"
                    },
                    destroy: {
                        url: function (course) {
                            return "api/registrations/" + course.Id;
                        },
                        contentType: 'application/json; charset=utf-8',
                        type: "DELETE"
                    },
                    parameterMap: function (data, type) {
                        // Convert to a JSON string.  Without this step your content will be form encoded.
                        return JSON.stringify(data);
                    }
                },
                schema: {
                    model: model
                },
                error: function (e) {
                    alert(e.errorThrown);
                },
                change: function (e) {
                    // فراخوانی در زمان دریافت اطلاعات از سرور و یا تغییرات محلی
                    viewModel.set("coursesDataSourceRows", new kendo.data.ObservableArray(this.view()));
                }
            });

            var viewModel = kendo.observable({
                //...
            });

            kendo.bind($("#coursesSection"), viewModel);
            dataSource.read(); // دریافت لیست موجود از سرور در آغاز کار
        });
    </script>
در اینجا تعریف DataSource کار با منبع داده راه دور ASP.NET Web API را مشاهده می‌کنید. تعاریف اصلی آن با تعاریف مطرح شده در مطلب «فعال سازی عملیات CRUD در Kendo UI Grid» یکی هستند. هر قسمت آن مانند read، create، update و destory به یکی از متدهای کنترلر ASP.NET Web API اشاره می‌کنند. حالت‌های update و destroy بر اساس Id ردیف انتخابی کار می‌کنند. این Id را باید در قسمت model مربوط به اسکیمای تعریف شده، دقیقا مشخص کرد. عدم تعریف فیلد id، سبب خواهد شد تا عملیات update نیز در حالت create تفسیر شود.


متصل کردن DataSource به ViewModel

تا اینجا DataSource ایی جهت کار با سرور تعریف شده‌است؛ اما مشخص نیست که اگر رکوردی اضافه شد، چگونه باید اطلاعات خودش را به روز کند. برای این منظور خواهیم داشت:
    <script type="text/javascript">
        $(function () {
            $("#coursesSection").kendoValidator({
                // ...
            });

            var model = kendo.data.Model.define({
                // ...
            });

            var dataSource = new kendo.data.DataSource({
                // ...
            });

            var viewModel = kendo.observable({
                accepted: false,
                course: new model(),
                doSave: function (e) {
                    e.preventDefault();
                    console.log("this", this.course);
                    var validator = $("#coursesSection").data("kendoValidator");
                    if (validator.validate()) {
                        if (this.course.Id == 0) {
                            dataSource.add(this.course);
                        }
                        dataSource.sync(); // push to the server
                        this.set("course", new model()); // reset controls
                    }
                },
                resetModel: function (e) {
                    e.preventDefault();
                    this.set("course", new model());
                }            
             });

            kendo.bind($("#coursesSection"), viewModel);
            dataSource.read(); // دریافت لیست موجود از سرور در آغاز کار
        });
    </script>
همانطور که در تعاریف تکمیلی viewModel مشاهده می‌کنید، اینبار دو متد جدید دلخواه doSave و resetModel را اضافه کرده‌ایم.
در متد doSave، ابتدا بررسی می‌کنیم آیا اعتبارسنجی فرم با موفقیت انجام شده‌است یا خیر. اگر بله، توسط متد add منبع داده، اطلاعات فرم جاری را توسط شیء course که هم اکنون به تمامی فیلدهای آن متصل است، اضافه می‌کنیم. در اینجا بررسی شده‌است که آیا Id این اطلاعات صفر است یا خیر. از آنجائیکه از همین متد برای به روز رسانی نیز در ادامه استفاده خواهد شد، در حالت به روز رسانی، Id شیء ثبت شده، از طرف سرور دریافت می‌گردد. بنابراین غیر صفر بودن این Id به معنای عملیات به روز رسانی است و در این حالت نیازی نیست کار بیشتری را انجام داد؛ زیرا شیء متناظر با آن پیشتر به منبع داده اضافه شده‌است.
استفاده از متد add صرفا به معنای مطلع کردن منبع داده محلی از وجود رکوردی جدید است. برای ارسال این تغییرات به سرور، از متد sync آن می‌توان استفاده کرد. متد sync بر اساس متد add یک درخواست POST، بر اساس شیءایی که Id غیر صفر دارد، یک درخواست PUT و با فراخوانی متد remove بر روی منبع داده، یک درخواست DELETE را به سمت سرور ارسال می‌کند.
متد دلخواه  resetModel سبب مقدار دهی مجدد شیء course با یک وهله‌ی جدید از شیء model می‌شود. همینقدر برای پاک کردن تمامی کنترل‌های صفحه کافی است.

تا اینجا دو متد جدید را در ViewModel برنامه تعریف کرده‌ایم. در مورد نحوه‌ی اتصال آن‌ها به View، به کدهای دو دکمه‌ی موجود در فرم دقت کنید:
<button class="k-button"
        data-bind="enabled: accepted, click: doSave"
        type="submit">
       ارسال
</button>
<button class="k-button" data-bind="click: resetModel">از نو</button>
این متدها نیز توسط ویژگی‌های data-bind به هر دکمه نسبت داده شده‌اند. به این ترتیب برای مثال با کلیک کاربر بر روی دکمه‌ی submit، متد doSave موجود در ViewModel فراخوانی می‌شود.


مدیریت سمت سرور ثبت، ویرایش و حذف اطلاعات

در حالت ثبت، متد Post توسط آدرس مشخص شده در قسمت create منبع داده، فراخوانی می‌گردد. نکته‌ی مهمی که در اینجا باید به آن دقت داشت، نحوه‌ی بازگشت Id رکورد جدید ثبت شده‌است.  اگر این تنظیم صورت نگیرد، Id رکورد جدید را در لیست، مساوی صفر مشاهده خواهید کرد و منبع داده این رکورد را همواره به عنوان یک رکورد جدید، مجددا به سرور ارسال می‌کند.
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Web.Http;
using KendoUI07.Models;

namespace KendoUI07.Controllers
{
    public class RegistrationsController : ApiController
    {
        public HttpResponseMessage Delete(int id)
        {
            var item = RegistrationsDataSource.LatestRegistrations.FirstOrDefault(x => x.Id == id);
            if (item == null)
                return Request.CreateResponse(HttpStatusCode.NotFound);

            RegistrationsDataSource.LatestRegistrations.Remove(item);
            return Request.CreateResponse(HttpStatusCode.OK, item);
        }

        public IEnumerable<Registration> Get()
        {
            return RegistrationsDataSource.LatestRegistrations;
        }

        public HttpResponseMessage Post(Registration registration)
        {
            if (!ModelState.IsValid)
                return Request.CreateResponse(HttpStatusCode.BadRequest);

            var id = 1;
            var lastItem = RegistrationsDataSource.LatestRegistrations.LastOrDefault();
            if (lastItem != null)
            {
                id = lastItem.Id + 1;
            }
            registration.Id = id;
            RegistrationsDataSource.LatestRegistrations.Add(registration);

            // ارسال آی دی مهم است تا از ارسال رکوردهای تکراری جلوگیری شود
            return Request.CreateResponse(HttpStatusCode.Created, registration);
        }

        [HttpPut] // Add it to fix this error: The requested resource does not support http method 'PUT'
        public HttpResponseMessage Update(int id, Registration registration)
        {
            var item = RegistrationsDataSource.LatestRegistrations
                                        .Select(
                                            (prod, index) =>
                                                new
                                                {
                                                    Item = prod,
                                                    Index = index
                                                })
                                        .FirstOrDefault(x => x.Item.Id == id);
            if (item == null)
                return Request.CreateResponse(HttpStatusCode.NotFound);


            if (!ModelState.IsValid || id != registration.Id)
                return Request.CreateResponse(HttpStatusCode.BadRequest);

            RegistrationsDataSource.LatestRegistrations[item.Index] = registration;
            return Request.CreateResponse(HttpStatusCode.OK);
        }
    }
}
در اینجا بیشتر امضای این متدها مهم هستند، تا منطق پیاده سازی شده در آن‌ها. همچنین بازگشت Id رکورد جدید، توسط متد Post نیز بسیار مهم است و سبب می‌شود تا DataSource بداند با فراخوانی متد sync آن، باید عملیات Post یا create انجام شود یا Put و update.


نمایش آنی اطلاعات ثبت شده در یک لیست

ردیف‌های اضافه شده به منبع داده را می‌توان بلافاصله در همان سمت کلاینت توسط Kendo UI Template که قابلیت کار با ViewModelها را دارد، نمایش داد:
    <div id="coursesSection" class="k-rtl k-header">
        <div class="box-col">
            <form id="myForm" data-role="validator" novalidate="novalidate">
                           <!--فرم بحث شده در ابتدای مطلب-->
            </form>
        </div>
        <div id="results">
            <table class="metrotable">
                <thead>
                    <tr>
                        <th>Id</th>
                        <th>نام</th>
                        <th>دوره</th>
                        <th>هزینه</th>
                        <th>ایمیل</th>
                        <th>تلفن</th>
                        <th></th>
                        <th></th>
                    </tr>
                </thead>
                <tbody data-template="row-template" data-bind="source: coursesDataSourceRows"></tbody>
                <tfoot data-template="footer-template" data-bind="source: this"></tfoot>
            </table>
            <script id="row-template" type="text/x-kendo-template">
                <tr>
                    <td data-bind="text: Id"></td>
                    <td data-bind="text: UserName"></td>
                    <td dir="ltr" data-bind="text: CourseName"></td>
                    <td>
                        #: kendo.toString(get("Credit"), "c0") #
                    </td>
                    <td data-bind="text: Email"></td>
                    <td data-bind="text: Tel"></td>
                    <td><button class="k-button" data-bind="click: deleteCourse">حذف</button></td>
                    <td><button class="k-button" data-bind="click: editCourse">ویرایش</button></td>
                </tr>
            </script>
            <script id="footer-template" type="text/x-kendo-template">
                <tr>
                    <td colspan="3"></td>
                    <td>
                        جمع کل: #: kendo.toString(totalPrice(), "c0") #
                    </td>
                    <td colspan="2"></td>
                    <td></td>
                    <td></td>
                </tr>
            </script>
        </div>
    </div>
در ناحیه‌ی coursesSection که توسط متد kendo.bind به viewModel برنامه متصل شده‌است، یک جدول را برای نمایش ردیف‌های ثبت شده توسط کاربر اضافه کرده‌ایم. thead آن بیانگر سر ستون جدول است. قسمت tbody و tfoot این جدول توسط دو Kendo UI Template مقدار دهی شد‌ه‌اند. هر کدام نیز منبع داده‌اشان را از view model دریافت می‌کنند. در row-template معادل خواص شیء course را مشاهده می‌کنید. در footer-template متد totalPrice برای نمایش جمع ستون هزینه اضافه شده‌است. بنابراین مطابق این قسمت از View، به یک خاصیت جدید coursesDataSourceRows و سه متد deleteCourse، editCourse و totalPrice نیاز است:
    <script type="text/javascript">
        $(function () {
            // ...
            var viewModel = kendo.observable({
                accepted: false,
                course: new model(),
                coursesDataSourceRows: new kendo.data.ObservableArray([]),
                doSave: function (e) {
                       // ...
                },
                resetModel: function (e) {
                      // ...
                },
                totalPrice: function () {
                    var sum = 0;
                    $.each(this.get("coursesDataSourceRows"), function (index, item) {
                        sum += item.Credit;
                    });
                    return sum;
                },
                deleteCourse: function (e) {
                    // the current data item is passed as the "data" field of the event argument
                    var course = e.data;
                    dataSource.remove(course);
                    dataSource.sync(); // push to the server
                },
                editCourse: function(e) {
                    // the current data item is passed as the "data" field of the event argument
                    var course = e.data;
                    this.set("course", course);
                }
            });

            kendo.bind($("#coursesSection"), viewModel);
            dataSource.read(); // دریافت لیست موجود از سرور در آغاز کار
        });
    </script>
نحوه‌ی اتصال خاصیت جدید coursesDataSourceRows که به عنوان منبع داده ردیف‌های row-template عمل می‌کند، به این صورت است:
- ابتدا خاصیت دلخواه coursesDataSourceRows به viewModel اضافه می‌شود تا در ناحیه‌ی coursesSection در دسترس قرار گیرد.
- سپس اگر به انتهای تعریف DataSource دقت کنید، داریم:
    <script type="text/javascript">
        $(function () {
            var dataSource = new kendo.data.DataSource({
                //...
                change: function (e) {
                    // فراخوانی در زمان دریافت اطلاعات از سرور و یا تغییرات محلی
                    viewModel.set("coursesDataSourceRows", new kendo.data.ObservableArray(this.view()));
                }
            });
        });
    </script>
متد change آن، هر زمانیکه اطلاعاتی در منبع داده تغییر کنند یا اطلاعاتی به سمت سرور ارسال یا دریافت گردد، فراخوانی می‌شود. در همینجا فرصت خواهیم داشت تا خاصیت coursesDataSourceRows را جهت نمایش اطلاعات موجود در منبع داده، مقدار دهی کنیم. همین مقدار دهی ساده سبب اجرای row-template برای تولید ردیف‌های جدول می‌شود. استفاده از new kendo.data.ObservableArray سبب خواهد شد تا اگر اطلاعاتی در فرم برنامه تغییر کند، این اطلاعات بلافاصله در لیست گزارش برنامه نیز منعکس گردد.



کدهای کامل این مثال را از اینجا می‌توانید دریافت کنید:
KendoUI07.zip
مطالب
قسمت اول - ساخت گزارش در محیط Telerik Reporting
یکی از ضروریات نرم افزارها وجود گزارشات مختلف در قالب لیست‌ها ، نمودارها و ... در آنها می‌باشد.یک نرم افزار خوب باید توانایی ارائه گزارشات خوب و زیبا را نیز داشته باشد.گزارشات در حقیقت نمایشی از داده‌ها هستند که عموما به چاپ می‌رسند. مورد دیگری که در خصوص گزارشات حائز اهمیت می‌باشد ، تبدیل آنها به فرمت‌های مختلف جهت حمل و جابه جایی آسان از سیستمی به سیستم دیگر می‌باشد. برای مثال تبدیل گزارشات به قالب Pdf مزایایی بسیاری از نظر قابل حمل بودن در پی خواهد داشت. در برنامه نویسی دات نت گزارشات را می‌توان توسط دستورات چاپ در دات تهیه ، نمایش و چاپ نمود. اما تهیه گزارش توسط دستورات دات نت کاری مشکل و طاقت فرسا می‌باشد. همچنین امکان تبدیل این گزارشات به فرمت‌های دیگر نظیر Pdf ، به راحتی انجام نمی‌شود و باید از کلاس‌ها و ابزارهای جانبی که برای این کار تهیه شده اند استفاده نمود . از این رو ابزارهای مختلفی در جهت تهیه گزارشات به وجود آمدند.ابزارهایی نظیر :

• Crystal Report
• Stimul Report
• Telerik Reporting
• … 

در ادامه این سری آموزش‌ها قصد داریم Telerik Reporting و نحوه تهیه گزارش با آن را مورد بررسی قرار دهیم. این ابزار امکانات بسیاری در خصوص تهیه گزارش برنامه‌های دات نتی نظیر Windows Form ، Asp.net و ... در اختیار ما قرار می‌دهد.در ادامه و برای شروع ، ساخت یک گزارش ساده در این محیط را بررسی میکنیم.

 
 نکته : گزارشاتی که توسط Telerik Reporting تهیه می‌شوند به وسیله کدهای C# جنریت می‌شوند.بنابراین همیشه توصیه می‌شود گزارشات خود را درون یک یا چند پروژه Class Library قرار دهیم و از این پس ، این گزارشات از درون پروژه‌های دیگر (ویندوزی ، وب و ...) در دسترس هستند.کافی ست پروژه Class Library را به عنوان Reference به پروژه مورد نظر خود اضافه کنیم..  برای شروع می‌توان یک پروژه جدید  از نوع Class Library  ایجاد کرد.پس از آن روی نام پروژه راست کلیک کنید و گزینه Telerik Report را انتخاب نمایید.پس از تعیین نام گزارش کلید Ok را کلیک نمایید.

انتخاب گزینه Telerik Reporting از پنجره New Item


در این حالت فایل گزارش به پروژه افزوده می‌شود. در ادامه می‌توانید توسط ویزاردی که نمایش داده می‌شود کارهای عمومی مربوط به پیاده سازی گزارش (انتخاب منبع داده(Data Source) ، ساخت Query جهت بارگذاری اطلاعات ، فیلدهایی که باید نمایش داده شوند ، گروه بندی داده‌ها و ...) را توسط این ویزارد انجام دهید. برای اینکار در پنجره ای که نمایش داده می‌شود بر روی کلید Next کلیک نمایید.
جهت ایجاد یک گزارش جدید در پنجره  Report Choose Page گزینه New Report را انتخاب نموده و کلید Next را کلیک نمایید.
جهت انتخاب منبع داده گزارش در پنجره Choose Data Source گزینه Add New Data Source را انتخاب نمایید.در این حالت می‌توانید گزینه‌های متفاوتی را به عنوان منبع داده گزارش خود انتخاب نمایید. گزینه‌های نمایش داده شده به شرح ذیل است:
• Sql Data Source : جهت اتصال مستقیم به بانک اطلاعات Microsoft Sql Server
• Object Data Source :  جهت اتصال به کلاس‌های لایه Business و بارگذاری داده از این کلاس ها
• Entity Data Source : جهت اتصال به  Entity Framework
• Open Access Data Source : جهت اتصال به Open Access ORM ساخت شرکت Telerik
• Cube Data Source : جهت اتصال و نمایش داده‌های تحلیل شده
  در ادامه برای اینکه بتوان مستقیما به Sql Server وصل شد و Query‌های مربوط به گزارش را روی آن اجرا نمود؛ می‌توان گزینه Sql Data Source را انتخاب نمود و بر روی کلید Ok کلیک کرد.سپس در پنجره Choose Your Data Connection گزینه New Connection را کلیک کنید و یک اتصال به بانک مورد نظر خود ایجاد کنید.پس ایجاد و تست Connection ساخته شده روی Next  کلیک کنید.در پنجره Save the connection string می‌توان نامی را جهت Connection string انتخاب کرد تا Connection string با همان نام در فایل Config پروژه ذخیره شود.در ادامه کلید Next را کلیک کرده و وارد مرحله بعد شوید. در پنجره Configure Data Source Command گزینه Query Builder را جهت ساخت Query مورد نظر برای بارگذاری داده‌ها انتخاب نمایید.

انتخاب جداول جهت ساخت Query


پنجره ساخت Query

پس از ساخت Query مورد نظر کلید Ok را کلیک نمایید. در پنجره Configure Data Source Command کوئری ساخته شده به شما نمایش داده می‌شود.کلید Next را کلیک کنید. 

سپس وارد مرحله Preview Data Source Result می‌شوید که در آن قادر خواهید بود پیش نمایشی از داده هایی که بعدا توسط Query ساخته شده بارگذاری خواهند شد را مشاهده نمایید. Next را کلیک نموده تا وارد مرحله بعد شوید.مرحله بعد Standard Report Type می‌باشد که در این مرحله شما می‌توانید نوع گزارش خود را انتخاب نمایید و کلید Next را فشار دهید.در بخش Design Data Layout چند فیلد را از بخش سمت چپ (Available Fields) انتخاب نموده و کلید Details را کلیک نمایید.فیلدهای انتخاب شده به بخش Details گزارش اضافه خواهند شد.در ادامه Next را کلیک کنید تا وارد بخش Choose Report Layout شوید.شما می‌توانید در این بخش یک حالت نمایشی را برای گزارش خود انتخاب نمایید و Next را کلیک نمایید.در بخش Choose Report Style یک قالب بندی جهت گزارش خود انتخاب نمایید.در ادامه Next و سپس Finish را کلیک نمایید.کدهای گزارش Generate شده می‌توان در قسمت Designer گزارش را مشاهده نمود. 

در این حالت کارهای زیر توسط Wizard به صورت اتوماتیک انجام خواهد شد:
• بایند شدن اتوماتیک فیلدهای گزارش به ستوان‌های مرتبط
• اعمال قالب بندی انتخاب شده برای صفحه و سر ستونها
• افزودن تاریخ و شماره صفحه به پایین گزارش
در ادامه پروژه را Rebuild کرده و گزینه Preview را در Designer جهت نمایش ، پیش نمایش گزارش کلیک نمایید.

نکته : در هر برنامه‌ی گزارش سازی بخش Designer گزارش به 4 بخش کلی تقسیم می‌شود:
•  Report Header: مواردی که در این بخش از گزارش قرار میگیرند در بالای صفحه اول گزارش نمایش داده می‌شوند.
•  Page Header: مواردی که در این بخش از گزارش قرار میگیرند در بالای همه صفحات گزارش قرار گرفته و تکرار می‌شوند.
•  Details: داده‌های اصلی گزارش که شامل جزئیات و بخش اصلی گزارش می‌باشند و سطر به سطر نیز تکرار می‌شوند در این بخش قرار می‌گیرند.
•  Page Footer: مواردی که در این بخش از گزارش قرار میگیرند در پایین همه صفحات نمایش داده می‌شوند.
•  Report Footer:مواردی که در این بخش قرار می‌گیرند در پایین صفحه آخر گزارش نمایش داده می‌شوند.  

ادامه دارد ...
مطالب دوره‌ها
پیاده سازی دکمه «بیشتر» یا «اسکرول نامحدود» به کمک jQuery در ASP.NET MVC
مدتی است در اکثر سایت‌ها و طراحی‌های جدید، به جای استفاده از روش متداول نمایش انتخاب صفحه 1، 2 ... 100، برای صفحه بندی اطلاعات، از روش اسکرول نامحدود یا infinite scroll استفاده می‌کنند. نمونه‌ای از آن‌را هم در سایت جاری با دکمه «بیشتر» در ذیل اکثر صفحات و مطالب سایت مشاهده می‌کنید.


در ادامه قصد داریم نحوه پیاده سازی آن‌را در ASP.NET MVC به کمک امکانات jQuery بررسی کنیم.


مدل برنامه

namespace jQueryMvcSample02.Models
{
    public class BlogPost
    {
        public int Id { set; get; }
        public string Title { set; get; }
        public string Body { set; get; }
    }
}
در این برنامه و مثال، قصد داریم لیستی از مطالب را توسط اسکرول نامحدود، نمایش دهیم. هر آیتم نمایش داده شده، ساختاری همانند کلاس BlogPost دارد.


منبع داده فرضی برنامه

using System.Collections.Generic;
using System.Linq;
using jQueryMvcSample02.Models;

namespace jQueryMvcSample02.DataSource
{
    public static class BlogPostDataSource
    {
        private static IList<BlogPost> _cachedItems;
        /// <summary>
        /// با توجه به استاتیک بودن سازنده کلاس، تهیه کش، پیش از سایر فراخوانی‌ها صورت خواهد گرفت
        /// باید دقت داشت که این فقط یک مثال است و چنین کشی به معنای
        /// تهیه یک لیست برای تمام کاربران سایت است
        /// </summary>
        static BlogPostDataSource()
        {
            _cachedItems = createBlogPostsInMemoryDataSource();
        }

        /// <summary>
        /// هدف صرفا تهیه یک منبع داده آزمایشی ساده تشکیل شده در حافظه است
        /// </summary>        
        private static IList<BlogPost> createBlogPostsInMemoryDataSource()
        {
            var results = new List<BlogPost>();
            for (int i = 1; i < 30; i++)
            {
                results.Add(new BlogPost { Id = i, Title = "عنوان " + i, Body = "متن ... متن ... متن " + i });
            }
            return results;
        }

        /// <summary>
        /// پارامترهای شماره صفحه و تعداد رکورد به ازای یک صفحه برای صفحه بندی نیاز هستند
        /// شماره صفحه از یک شروع می‌شود
        /// </summary>
        public static IList<BlogPost> GetLatestBlogPosts(int pageNumber, int recordsPerPage = 4)
        {
            var skipRecords = pageNumber * recordsPerPage;
            return _cachedItems
                        .OrderByDescending(x => x.Id)
                        .Skip(skipRecords)
                        .Take(recordsPerPage)
                        .ToList();
        }
    }
}
برای اینکه برنامه نهایی را به سادگی بتوانید اجرا کنید، به عمد از بانک اطلاعاتی خاصی استفاده نشده و صرفا یک منبع داده فرضی تشکیل شده در حافظه، در اینجا مورد استفاده قرار گرفته است. بدیهی است قسمت cachedItems را به سادگی می‌توانید با یک ORM جایگزین کنید.
تنها نکته مهم آن، نحوه تعریف متد GetLatestBlogPosts می‌باشد که برای صفحه بندی اطلاعات بهینه سازی شده است. در اینجا توسط متدهای Skip و Take، تنها بازه‌ای از اطلاعات که قرار است نمایش داده شوند، دریافت می‌گردد. خوشبختانه این متدها معادل‌های مناسبی را در اکثر بانک‌های اطلاعاتی داشته و استفاده از آن‌ها بر روی یک بانک اطلاعاتی واقعی نیز بدون مشکل کار می‌کند و تنها بازه محدودی از اطلاعات را واکشی خواهد کرد که از لحاظ مصرف حافظه و سرعت کار بسیار مقرون به صرفه و سریع است.


کنترلر برنامه

using System.Linq;
using System.Web.Mvc;
using System.Web.UI;
using jQueryMvcSample02.DataSource;
using jQueryMvcSample02.Security;

namespace jQueryMvcSample02.Controllers
{
    public class HomeController : Controller
    {
        [HttpGet]
        public ActionResult Index()
        {
            //آغاز کار با صفحه صفر است
            var list = BlogPostDataSource.GetLatestBlogPosts(pageNumber: 0);
            return View(list); //نمایش ابتدایی صفحه
        }

        [HttpPost]
        [AjaxOnly]
        [OutputCache(Location = OutputCacheLocation.None, NoStore = true)]
        public virtual ActionResult PagedIndex(int? page)
        {
            var pageNumber = page ?? 0;
            var list = BlogPostDataSource.GetLatestBlogPosts(pageNumber);
            if (list == null || !list.Any())
                return Content("no-more-info"); //این شرط ما است برای نمایش عدم یافتن رکوردها

            return PartialView("_ItemsList", list);
        }

        [HttpGet]
        public ActionResult Post(int? id)
        {
            if (id == null)
                return Redirect("/");

            //todo: show the content here
            return Content("Post " + id.Value);
        }
    }
}
کنترلر برنامه را در اینجا ملاحظه می‌کنید. برای کار با اسکرول نامحدود، به ازای هر صفحه، نیاز به دو متد است:
الف) یک متد که بر اساس HttpGet کار می‌کند. این متد در اولین بار نمایش صفحه فراخوانی می‌گردد و اطلاعات صفحه آغازین را نمایش می‌دهد.
ب) متد دومی که بر اساس HttpPost کار کرده و محدود است به درخواستی‌های AjaxOnly همانند متد PagedIndex.
از این متد دوم برای پردازش کلیک‌های کاربر بر روی دکمه «بیشتر» استفاده می‌گردد. بنابراین تنها کاری که افزونه جی‌کوئری تدارک دیده شده ما باید انجام دهد، ارسال شماره صفحه است. سپس با استفاده از این شماره، بازه مشخصی از اطلاعات دریافت و نهایتا یک PartialView رندر شده برای افزوده شدن به صفحه بازگشت داده می‌شود.


دو View برنامه

همانطور که برای بازگشت اطلاعات نیاز به دو اکشن متد است، برای رندر اطلاعات نیز به دو View نیاز داریم:
الف) یک PartialView که صرفا لیستی از اطلاعات را مطابق سلیقه ما رندر می‌کند. از این PartialView در متد PagedIndex استفاده خواهد شد:
@model IList<jQueryMvcSample02.Models.BlogPost>
<ul>
    @foreach (var item in Model)
    {
        <li>
            <h5>
                @Html.ActionLink(linkText: item.Title,
                                 actionName: "Post",
                                 controllerName: "Home",
                                 routeValues: new  { id = item.Id },
                                 htmlAttributes: null)
            </h5>
            @item.Body
        </li>
    }
</ul>
ب) یک View کامل که در بار اول نمایش صفحه، مورد استفاده قرار می‌گیرد:
@model IList<jQueryMvcSample02.Models.BlogPost>
@{
    ViewBag.Title = "Index";
    var loadInfoUrl = Url.Action(actionName: "PagedIndex", controllerName: "Home");
}
<h2>
    اسکرول نامحدود</h2>
@{ Html.RenderPartial("_ItemsList", Model); }
<div id="MoreInfoDiv">
</div>
<div align="center" style="margin-bottom: 9px;">
    <span id="moreInfoButton" style="width: 90%;" class="btn btn-info">بیشتر</span>
</div>
<div id="ProgressDiv" align="center" style="display: none">
    <br />
    <img src="@Url.Content("~/Content/images/loadingAnimation.gif")" alt="loading..."  />
</div>
@section JavaScript
{
    <script type="text/javascript">
        $(document).ready(function () {
            $("#moreInfoButton").InfiniteScroll({
                moreInfoDiv: '#MoreInfoDiv',
                progressDiv: '#ProgressDiv',
                loadInfoUrl: '@loadInfoUrl',
                loginUrl: '/login',
                errorHandler: function () {
                    alert('خطایی رخ داده است');
                },
                completeHandler: function () {
                    // اگر قرار است روی اطلاعات نمایش داده شده پردازش ثانوی صورت گیرد
                },
                noMoreInfoHandler: function () {
                    alert('اطلاعات بیشتری یافت نشد');
                }
            });
        });
    </script>
}
چند نکته در اینجا حائز اهمیت است:
1) مسیر دقیق اکشن متد PagedIndex توسط متد  Url.Action تهیه شده است.
2) در ابتدای نمایش صفحه، متد Html.RenderPartial کار نمایش اولیه اطلاعات را انجام خواهد داد.
3) از div خالی MoreInfoDiv، به عنوان محل افزوده شدن اطلاعات Ajax ایی دریافتی استفاده می‌کنیم.
4) دکمه بیشتر در اینجا تنها یک span ساده است که توسط css به شکل یک دکمه نمایش داده خواهد شد (فایل‌های آن در پروژه پیوست موجود است).
5) ProgressDiv در ابتدای نمایش صفحه مخفی است. زمانیکه کاربر بر روی دکمه بیشتر کلیک می‌کند، توسط افزونه جی‌کوئری ما نمایان شده و در پایان کار مجددا مخفی می‌گردد.
6) section JavaScript کار استفاده از افزونه InfiniteScroll را انجام می‌دهد.


و کدهای افزونه اسکرول نامحدود

// <![CDATA[
(function ($) {
    $.fn.InfiniteScroll = function (options) {
        var defaults = {
            moreInfoDiv: '#MoreInfoDiv',
            progressDiv: '#Progress',
            loadInfoUrl: '/',
            loginUrl: '/login',
            errorHandler: null,
            completeHandler: null,
            noMoreInfoHandler: null
        };
        var options = $.extend(defaults, options);

        var showProgress = function () {
            $(options.progressDiv).css("display", "block");
        }

        var hideProgress = function () {
            $(options.progressDiv).css("display", "none");
        }       

        return this.each(function () {
            var moreInfoButton = $(this);
            var page = 1;
            $(moreInfoButton).click(function (event) {
                showProgress();
                $.ajax({
                    type: "POST",
                    url: options.loadInfoUrl,
                    data: JSON.stringify({ page: page }),
                    contentType: "application/json; charset=utf-8",
                    dataType: "json",
                    complete: function (xhr, status) {
                        var data = xhr.responseText;
                        if (xhr.status == 403) {
                            window.location = options.loginUrl;
                        }
                        else if (status === 'error' || !data) {
                            if (options.errorHandler)
                                options.errorHandler(this);
                        }
                        else {
                            if (data == "no-more-info") {
                                if (options.noMoreInfoHandler)
                                    options.noMoreInfoHandler(this);
                            }
                            else {
                                var $boxes = $(data);
                                $(options.moreInfoDiv).append($boxes);
                            }
                            page++;
                        }
                        hideProgress();
                        if (options.completeHandler)
                            options.completeHandler(this);
                    }
                });
            });
        });
    };
})(jQuery);
// ]]>
ساختار افزونه اسکرول نامحدود به این شرح است:
هربار که کاربر بر روی دکمه بیشتر کلیک می‌کند، progress div ظاهر می‌گردد. سپس توسط امکانات jQuery Ajax، شماره صفحه (بازه انتخابی) به اکشن متد صفحه بندی اطلاعات ارسال می‌گردد. در نهایت اطلاعات را از کنترلر دریافت و به moreInfoDiv اضافه می‌کند. در آخر هم شماره صفحه را یکی افزایش داده و سپس progress div را مخفی می‌کند.

دریافت مثال و پروژه کامل این قسمت
jQueryMvcSample02.zip
 
بازخوردهای پروژه‌ها
Helper AjaxDialog چطوری کار میکنه؟
سلام
توی پروژه از یک Helper برای نمایش Dialog وار View‌ها استفاده شده
که همون AjaxDialog هست که شامل LinkDialog و ButtonDialog می‌باشد
سوال اینجا پیش اومده
که این داره چطوری کار میکنه؟
آیا امکان داره توی یک پروژه دیگه هم ازش استفاده کرد؟
اگه اره به جز DialogOptions به چه چیزی نیاز داره؟
مرسی
مطالب
برنامه نویسی پیشرفته JavaScript - قسمت 6 - تغییر صفات Property ها

برنامه نویسی شیء گرا

در این بخش میخواهیم به بررسی یکسری از ویژگی‌ها و نکات ریز برنامه نویسی شیء گرا در جاوا اسکریپت بپردازیم که یک برنامه نویس حرفه‌ای جاوا اسکریپت حتما باید بر آن‌ها واقف باشد تا بتواند کتابخانه‌ها و Framework ‌های موثرتر و بهینه‌تری را ایجاد کند. لازم به ذکر است که در این مجموعه مقالات، پیاده‌سازی اشیاء و شیوه‌ی کد نویسی، بر اساس استاندارد ECMAScript 5 یا ES5 انجام خواهد شد. بنابراین از قابلیتهای جدیدی که در ES6 اضافه شده‌است، صحبت نخواهیم کرد. پس از پایان این مجموعه مقالات و پس از آگاهی کامل از قابلیتهای جاوا اسکریپت، در مجموعه مقالاتی به بررسی قابلیتهای جدید ES6 خواهیم پرداخت که مرتبط به مقالات جاری است.

همانطور که قبلا اشاره شد، در زبان‌های برنامه نویسی شیء گرا، مفهومی به نام کلاس وجود دارد که ساختاری را جهت ایجاد اشیاء معرفی می‌کند و میتوانیم اشیاء مختلفی را از این کلاس‌ها ایجاد نماییم. اما در جاوا اسکریپت مفهوم کلاس وجود ندارد و فقط می‌توانیم از اشیاء استفاده کنیم که نسبت به زبان‌های مبتنی بر کلاس متفاوت می‌باشد.

بر اساس تعریفی که از اشیاء در استاندارد ECMAScript صورت گرفته است، هرشیء، شامل مجموعه‌ای از ویژگی‌هاست، که هر یک از آنها می‌تواند حاوی یک مقدار پایه، شیء و یا تابع باشد. به عبارت دیگر هر شیء شامل آرایه‌ای از مقادیر است. هر ویژگی ( Property ) یا تابع (که در برنامه نویسی شیء گرا متد نیز نامیده می‌شود) توسط نام خود شناسایی می‌شوند که به یک مقدار داده‌ای نگاشت یا Map شده‌اند. به همین دلیل میتوان هر شیء را به عنوان یک Hash Table تصور کرد که داده‌ها را به صورت یک زوج کلید مقدار یا key-value pairs نگهداری می‌نماید. در اینصورت نام ویژگی‌ها و متدها به عنوان key و مقدار آنها به عنوان value در نظر گرفته می‌شوند.


مفهوم شیء

همانطور که قبلا اشاره شد، جهت تعریف اشیاء می‌توان از دو روش استفاده نمود. در روش اول، ایجاد شیء با استفاده از شیء Object و در روش دوم، با استفاده از Object Literal Notation انجام خواهد شد. روش دوم جدیدتر و بین برنامه نویسان جاوا اسکریپت محبوب‌تر است. مثال دیگری را جهت یادآوری در این مورد ذکر می‌کنم:

var person = new Object();
person.firstName = "Meysam";
person.birth = new Date(1982, 11, 8);
person.getAge = function () {
    var now = new Date();
    return now.getFullYear() - this.birth.getFullYear();
}

alert(person.firstName + ": " + person.getAge());    // Meysam: 34
در مثال فوق، شیء person شامل دو ویژگی firstName و birth و همچنین تابع getAge() می‌باشد. در تابع getAge() از روی ویژگی birth یا تاریخ تولد، سن شخص محاسبه شده‌است. همانطور که مشاهده می‌کنید، در داخل این تابع، جهت دسترسی به ویژگی birth، از شیء this استفاده نمودیم. this به شیء ای اشاره می‌کند که تابع getAge() به آن تعلق دارد و در اینجا به شیء person اشاره می‌نماید. اگر از this استفاده نکنید، برنامه خطا می‌دهد؛ زیرا قادر به شناسایی birth نمی‌باشد. مثال فوق را میتوان با استفاده از Object Literal Notation به صورت زیر نوشت:
var person = {
    firstName: "Meysam",
    birth: new Date(1982, 11, 8),
    getAge: function () {
        var now = new Date();
        return now.getFullYear() - this.birth.getFullYear();
    }
};

alert(person.firstName + ": " + person.getAge());    // Meysam: 34

انواع Property ها

در ECMAScript 5 ، صفاتی برای Property ‌ها معرفی شده است که از طریق Attribute ‌های داخلی به Property ‌ها اختصاص می‌یابد. این Attribute ‌ها توسط موتور جاوا اسکریپت بر روی Property ‌ها پیاده سازی می‌شوند و به صورت مستقیم قابل دسترسی نمی‌باشند. در طی فرآیند آموزش این مطالب، Attribute ‌های داخلی را در [[]] قرار می‌دهیم، مثل [[Enumarable]] ، تا از سایر دستورات تفکیک شوند. به صورت کلی دو نوع ویژگی داریم که شامل Data Properties و Accessor Properties می‌باشند که به شرح آنها می‌پردازیم.


Data Properties

Data Property ‌ها، 4 صفت یا Attribute را توصیف می‌کنند که عبارتند از:

[[Configurable]]

مشخص می‌کند یک Property اجازه حذف، تعریف مجدد و یا تغییر نوع را دارد یا خیر. بصورت پیش فرض، زمانی که یک شیء بصورت مستقیم ساخته می‌شود، مقدار این ویژگی True می‌باشد.

[[Enumarable]]

مشخص می‌کند که آیا امکان پیمایش یک Property توسط حلقه for-in وجود دارد یا خیر. بصورت پیش فرض، زمانیکه یک شیء بصورت مستقیم ساخته می‌شود، مقدار این ویژگی True می‌باشد.

[[Writable]]

مشخص می‌کند که آیا مقدار یک Property قابل تغییر می‌باشد یا خیر. بصورت پیش فرض، زمانیکه یک شیء بصورت مستقیم ساخته می‌شود، مقدار این ویژگی True می‌باشد.

[[Value]]

شامل مقدار واقعی یک Property و محل مقداردهی یا برگرداندن مقدار Property ‌ها می‌باشد. مقدار پیش فرض آن نیز undefined می‌باشد.


زمانیکه یک Property به صورت عادی به یک شیء اضافه می‌شود، مانند مثال‌های قبلی، سه Attribute اول به true تنظیم می‌شوند و [[Value]]  با مقدار اولیه Property تنظیم میگردد. در این حالت آن Property ، قابل بروزرسانی و پیمایش می‌باشد. جهت تغییر ساختار یک Property و تنظیم Attribute ‌های آن، باید آن Property را با استفاده از متد defineProperty() تعریف نماییم . شکل کلی تعریف Property با استفاده از این متد به صورت زیر می‌باشد:

Object.defineProperty(obj, prop, descriptor)
آرگومان obj ، شیء ای است که Property مورد نظر باید به آن اضافه شود. آرگومان prop نام Property را مشخص می‌کند که Attribute ‌های آن باید تنظیم شوند. آرگومان descriptor  یک شیء می‌باشد که  Attribute ‌های مورد نیاز را برای Property تنظیم می‌نماید. شیء descriptor شامل ویژگی‌های configurable ، enumerable ، writable و value می‌باشد که می‌توانند برای Property تنظیم شوند. خروجی این متد شیء ای است که به عنوان آرگومان اول ارسال شده‌است. به مثال‌های زیر توجه کنید:
var person = {};
Object.defineProperty(person, "name", {
    writable: false,
    value:"Meysam"
});

alert(person.name);   // Meysam
person.name = "Arash";
alert(person.name);   // Meysam
همانطور که در مثال فوق مشاهده می‌کنید، یک Property به نام name به شیء person اضافه شده‌است که صفت writable آن به false تنظیم گردیده‌است. بنابراین امکان تغییر مقدار ویژگی name وجود ندارد و با اینکه در دستور person.name = "Arash" ، ویژگی name را تغییر داده‌ایم، دستور alert نهایی، مجددا خروجی Meysam را نمایش داده‌است.
var person = {};
Object.defineProperty(person, "name", {
    configurable: false,
    value: "Meysam"
});

alert(person.name);  // Meysam
delete person.name;
alert(person.name);  // Meysam
در مثال فوق، صفت configurable را به false تنظیم نموده‌ایم و همانطور که مشاهده میکنید امکان حذف ویژگی name توسط عملگر delete وجود ندارد و دستور alert نهایی مجددا خروجی Meysam را نمایش داده‌است. توجه داشته باشید که اگر شما بخواهید در خطوط بعدی کد، مجددا صفت configurable را به مقدار true تغییر دهید، امکان پذیر نمی‌باشد. زیرا در تعریف فوق، صفت configurable را به false تنظیم نموده‌اید و امکان بروزرسانی Attribute ‌های ویژگی name را از آن گرفته‌اید. در این حالت تنها Attribute ی را که میتوانید تنظیم کنید، صفت writable می‌باشد.

لازم به ذکر است که می‌توانید متد defineProperty() را چندین بار برای یک Property فراخوانی نموده و در هر مرحله صفات متفاوتی را تنظیم و یا صفات قبلی را تغییر دهید.

علاوه بر متد فوق، متد دیگری به نام defineProperties() وجود دارد که می‌توان چند Property را بصورت همزمان تعریف و صفات آن را تنظیم نمود. شکل کلی این متد به صورت زیر است:

Object.defineProperties(obj, props)

آرگومان props یک شیء می‌باشد که ویژگی‌های آن، نام همان Property هایی هستند که باید به obj اضافه شوند. همچنین هر ویژگی خود یک شیء می‌باشد که میتوان صفات آن ویژگی را تنظیم نمود. به مثال زیر توجه کنید:

var person = {};
Object.defineProperties(person, {
    "name": {
        configurable: false,
        value: "Meysam"
    },
    "age": {
        writable:false,
        value:34
    }
});
در مثال فوق، برای آرگومان props ، دو ویژگی name و age را تعریف نمودیم که این دو ویژگی به شیء person اضافه خواهند شد. همچنین ویژگی‌های name و age خود یک شیء می‌باشند که صفات مربوط به آنها تنظیم شده است.

Accessor Properties

این صفات شامل توابع getter و setter می‌باشند که یک یا هر دوی آنها می‌توانند برای یک Property تنظیم شوند. زمانی که مقداری را از یک Property می‌خوانید، تابع getter فراخوانی می‌شود و مقدار Property مربوطه را بر میگرداند. این تابع می‌تواند قبل از برگرداندن مقدار، پردازش هایی را بر روی آن Property انجام داده و یک نتیجه‌ی معتبر را برگرداند. زمانیکه Property را مقداردهی می‌نمایید، تابع setter فراخوانی میشود و Property را با مقدار جدید تنظیم می‌نماید. این تابع می‌تواند قبل از مقداردهی به Property ، داده‌ی مورد نظر را اعتبارسنجی نماید تا از ورود مقادیر نامعتبر جلوگیری کند. Accessor Properties شامل 2 صفت زیر می‌باشد:

[[Get]]

یک تابع می‌باشد و زمانی فراخوانی می‌گردد که مقدار یک Property را بخوانیم و مقدار پیش فرض آن undefined می‌باشد.

[[Set]]

یک تابع می‌باشد و زمانی فراخوانی می‌گردد که یک Property را مقداردهی نماییم و مقدار پیش فرض آن undefined می‌باشد. این تابع شامل یک آرگومان ورودی است که حاوی مقدار ارسالی به Property است.

مثال زیر یک پیاده سازی ساده از شیء تاریخ شمسی می‌باشد که هنوز از لحاظ طراحی دارای نواقصی هست و در ادامه کارآیی و کد آن را بهبود می‌بخشیم.

var date = {
    _year: 1,
    _month: 1,
    _day: 1,
    isLeap: function () {
        switch (this.year % 33) {
            case 1: case 5: case 9: case 13:
            case 17: case 22: case 26: case 30:
                return true;
            default:
                return false;
        }
    }
};

Object.defineProperties(date, {
    "year": {
        "get": function () { return this._year; },
        "set": function (newValue) {
            if (newValue < 1 || newValue > 9999)
                throw new Error("Year must be between 1 and 9999");
            this._year = newValue;
        }
    },
    "month": {
        "get": function () { return this._month; },
        "set": function (newValue) {
            if (newValue < 1 || newValue > 12)
                throw new Error("Month must be between 1 and 12");
            this._month = newValue;
        }
    },
    "day": {
        "get": function () { return this._day; },
        "set": function (newValue) {
            if (newValue < 1 || newValue > 31)
                throw new Error("Day must be between 1 and 31");
            if (this.month === 12 && !this.isLeap() && newValue > 29)
                throw new Error("Day must be between 1 and 29");
            if (this.month > 6 && newValue > 30)
                throw new Error("Day must be between 1 and 30");
            this._day = newValue;
        }
    }
});
در مثال فوق، 3 ویژگی با نامهای _year ، _month و _day تعریف شده‌اند. پیشوند _ مشخص می‌کند که نباید به این ویژگی در خارج از شیء دسترسی داشته باشیم. البته دسترسی را محدود نمی‌کند و برنامه نویس به راحتی می‌تواند به آن دسترسی داشته باشد. در مباحث بعدی شیوه‌ی صحیح پیاده سازی اینگونه Property ‌ها را آموزش می‌دهیم. تابعی به نام isLeap() نیز تعریف شده است که تشخیص می‌دهد سال موجود کبیسه هست یا خیر. با استفاده از تابع defineProperties() ، 3 ویژگی دیگر نیز به شیء date ، با نامهای year ، month و day اضافه نموده‌ایم که دارای Accessor ‌های get و set می‌باشند. در بخش set ورودی‌های کاربران را بررسی و اعتبار سنجی نمودیم. در صورتی که ورودی نامعتبر باشد، با استفاده از throw خطایی را به صورت دستی ایجاد می‌نماییم که در console مربوط به Browser قابل مشاهده و یا با استفاده از try…catch قابل دسترسی و مدیریت می‌باشد.

دقت داشته باشید که لازم نیست حتما accessor ‌های getter و setter با هم برای یک Property تنظیم شوند و شما می‌توانید فقط یکی از آنها را برای Property به کار ببرید. اگر فقط تابع getter به یک Property اختصاص یابد، آن Property فقط خواندنی می‌شود و امکان تغییر مقدار آن وجود ندارد. در این صورت هر دستوری که اقدام به تغییر Property نماید، بی‌تاثیر خواهد بود. همچنین اگر فقط تابع setter به یک Property اختصاص یابد، آن Property فقط نوشتنی می‌شود و امکان خواندن مقدار آن وجود ندارد. در این صورت هر دستوری که اقدام به خواندن Property نماید، مقدار undefined برای آن برگردانده می‌شود.

نکته‌ی دیگری که باید به آن توجه کنید این است که اگر یک Property با استفاده از متد defineProperty() تعریف گردد، Attribute هایی که مقداردهی نشده‌اند، مثل [[Configurable]] ، [[Enumarable]] و [[Writable]] با false مقداردهی می‌گردند و [[Value]] ، [[Get]] و [[Set]] مقدار undefined را بر می‌گردانند. در مبحث بعدی، در مورد این نکته مثالی ارائه شده است.


خواندن Attribute ‌های مربوط به یک Property

با استفاده از متد getOwnPropertyDescriptor() می‌توان، Attribute ‌های اختصاص داده شده به Property ‌ها را خواند و از مقدار آنها مطلع شد. این متد شامل 2 آرگومان می‌باشد، که آرگومان اول، شیء ای است که میخواهیم Attribute آن را بخوانیم و آرگومان دوم، نام Attribute می‌باشد. خروجی متد getOwnPropertyDescriptor() یک شیء از نوع PropertyDescriptor می‌باشد که ویژگی‌های آن، همان Attribute هایی هستند که برای یک Property تنظیم شده‌اند. به مثال زیر جهت خواندن Attribute ‌های شیء تاریخ شمسی توجه کنید:

var descriptor = Object.getOwnPropertyDescriptor(date, "_year");
alert(descriptor.value);   // 1
alert(descriptor.configurable); // true
alert(typeof descriptor.get); // undefined

descriptor = Object.getOwnPropertyDescriptor(date, "year");
alert(descriptor.value);   // undefined
alert(descriptor.configurable); // false
alert(typeof descriptor.get); // function
ویژگی _year به صورت عادی تعریف شده است. بنابراین با توجه به نکاتی که قبلا ذکر شد، مقدار اختصاص داده شده به این ویژگی، به صفت [[Value]] تعلق گرفته است. همچنین سایر صفات این ویژگی به مانند [[Configurable]] ، با مقدار true تنظیم شده‌اند. Accessor ‌های getter و setter نیز، که برای این ویژگی تنظیم نشده بودند، مقدار undefined بر می‌گردانند. ویژگی year با استفاده از متد defineProperties() تعریف شده است و چون Accessor ‌های getter و setter به آن اختصاص یافته‌اند، صفت [[Value]]، مقدار undefined را بر می‌گرداند و سایر Attribute ‌ها به مانند [[Configurable]] که تنظیم نشده‌اند، مقدار false را بر می‌گردانند. همچنین برای getter و setter نوع function برگردانده شده‌است. 
مطالب
اعمال کلاس‌های ویژه اعتبارسنجی Twitter bootstrap به فرم‌های ASP.NET MVC
اگر مطلب «استفاده از Twitter Bootstrap در کارهای روزمره طراحی وب» را مطالعه کرده باشید، قسمتی از آن، به فرم‌ها و همچنین جلب توجه کاربران به فیلدها، برای نمایش خطاهای اعتبارسنجی اختصاص داشت. در مطلب جاری قصد داریم تا این موارد را به یک فرم ASP.NET MVC که به صورت پیش فرض از jQuery Validator برای اعتبارسنجی استفاده می‌کند، اعمال کنیم تا حالت نمایشی پیش فرض این فرم‌ها و همچنین خطاهای اعتبارسنجی آن، با Twitter Bootstrap همخوانی پیدا کند.

مدل برنامه

using System.ComponentModel;
using System.ComponentModel.DataAnnotations;

namespace Mvc4TwitterBootStrapTest.Models
{
    public class User
    {
        [DisplayName("نام")]
        [Required(ErrorMessage="لطفا نام را تکمیل کنید")]
        public string Name { set; get; }

        [DisplayName("نام خانوادگی")]
        [Required(ErrorMessage = "لطفا نام خانوادگی را تکمیل کنید")]
        public string LastName { set; get; }
    }
}
در اینجا یک مدل ساده را به همراه دو خاصیت و اعتبارسنجی‌های ساده مرتبط با آن‌ها، مشاهده می‌کنید.

کنترلر برنامه

using System.Web.Mvc;
using Mvc4TwitterBootStrapTest.Models;

namespace Mvc4TwitterBootStrapTest.Controllers
{
    public class HomeController : Controller
    {
        [HttpGet]
        public ActionResult Index()
        {
            return View();
        }

        [HttpPost]
        public ActionResult Index(User user)
        {
            if (this.ModelState.IsValid)
            {
                if (user.Name != "Vahid")
                {
                    this.ModelState.AddModelError("", "لطفا مشکلات را برطرف کنید!");
                    this.ModelState.AddModelError("Name", "نام فقط باید وحید باشد!");
                    return View(user);
                }
                // todo: save ...
                return RedirectToAction("Index");
            }
            return View(user);
        }
    }
}
کنترلر برنامه نیز نکته مهمی نداشته و بیشتر برای نمایش خطاهای اعتبارسنجی سفارشی این مثال طراحی شده است.

طراحی View سازگار با Twitter bootstrap

@model Mvc4TwitterBootStrapTest.Models.User
@{
    ViewBag.Title = "تعریف کاربر";
}
@using (Html.BeginForm("Index", "Home", FormMethod.Post, new { @class = "form-horizontal" }))
{
    @Html.ValidationSummary(true, null, new { @class = "alert alert-error alert-block" })

    <fieldset>
        <legend>تعریف کاربر</legend>
        <div class="control-group">
            @Html.LabelFor(x => x.Name, new { @class = "control-label" })
            <div class="controls">
                @Html.TextBoxFor(x => x.Name)
                @Html.ValidationMessageFor(x => x.Name, null, new { @class = "help-inline" })
            </div>
        </div>
        <div class="control-group">
            @Html.LabelFor(x => x.LastName, new { @class = "control-label" })
            <div class="controls">
                @Html.TextBoxFor(x => x.LastName)
                @Html.ValidationMessageFor(x => x.LastName, null, new { @class = "help-inline" })
            </div>
        </div>
        <div class="form-actions">
            <button type="submit" class="btn btn-primary">
                ارسال</button>
        </div>
    </fieldset>
}
در اینجا View متناظر با اکشن متد Index را ملاحظه می‌کنید که نکات ذیل به آن اعمال شده است:
1) کلاس form-horizontal به فرم جاری اضافه شده است تا در ادامه بتوانیم برچسب‌ها را در کنار تکست باکس‌ها به صورت افقی نمایش دهیم.
2) به ValidationSummary کلاس‌های alert alert-error alert-block انتساب داده شده‌اند تا نمایش خطای کلی یک فرم، متناسب با Twitter bootstrap شود.
3) هر خاصیت، با یک div دارای کلاس control-group محصور شده است.
4) هر برچسب دارای کلاس control-label است.
5) به هر ValidationMessageFor کلاس help-inline انتساب داده شده است.
6) کنترل‌های ورودی برنامه در divایی با کلاس controls محصور شده‌اند.
7) قسمت دکمه فرم، در div ایی با کلاس form-actions قرار گرفته تا یک زمینه خاکستری در اینجا ظاهر شود.
8) دکمه فرم، با کلاس btn خاص bootstrap تزئین شده.

در این حالت به شکل فوق خواهیم رسید. همانطور که ملاحظه می‌کنید در صورتیکه بر روی دکمه ارسال کلیک شود، همان رنگ‌های متداول jQuery Validator ظاهر می‌شوند و کل ردیف همانند روش‌های متداول Twitter bootstrap دارای رنگ قرمز انتساب یافته توسط کلاس error نخواهد شد.

برای رفع این مشکل باید اندکی اسکریپت نویسی کرد:
@section javaScript
{
    <script type="text/javascript">
        $.validator.setDefaults({
            highlight: function (element, errorClass, validClass) {
                if (element.type === 'radio') {
                    this.findByName(element.name).addClass(errorClass).removeClass(validClass);
                } else {
                    $(element).addClass(errorClass).removeClass(validClass);
                    $(element).closest('.control-group').removeClass('success').addClass('error');
                }
                $(element).trigger('highlated');
            },
            unhighlight: function (element, errorClass, validClass) {
                if (element.type === 'radio') {
                    this.findByName(element.name).removeClass(errorClass).addClass(validClass);
                } else {
                    $(element).removeClass(errorClass).addClass(validClass);
                    $(element).closest('.control-group').removeClass('error').addClass('success');
                }
                $(element).trigger('unhighlated');
            }
        });

        $(function () {
            $('form').each(function () {
                $(this).find('div.control-group').each(function () {
                    if ($(this).find('span.field-validation-error').length > 0) {
                        $(this).addClass('error');
                    }
                });
            });
        });
    </script>
}
کاری که در اینجا انجام شده، تغییر پیش فرض‌های jQuery Validator جهت سازگار سازی آن با کلاس error مرتبط با bootstrap است. همچنین در حالت postback و نمایش خطاهای سفارشی، قسمت بررسی field-validation-error انجام شده و در صورت یافتن موردی، سطر مرتبط با آن، با کلاس error مزین می‌شود.

اینبار در حالت اعتبار سنجی، به شکل ذیل خواهیم رسید:

و یا اگر کاربر فیلد را تکمیل کند، رنگ فیلد و ردیف تعیین اعتبار شده، سبز می‌شود:

و در حالت خطاهای سفارشی سمت سرور، پس از postback، شکل زیر نمایش داده می‌شود:


مطالب
آموزش (jQuery) جی کوئری 2#
در ادامه مطلب قبلی آموزش (jQuery) جی کوئری 1# به ادامه بحث می‌پردازیم.

توابع سودمند
با وجود آنکه انتخاب کردن و ایجاد مجموعه ای از عناصر صفحه یکی از معمول‌ترین و پراستفاده‌ترین کاربردهای تابع ()$ محسوب می‌شود، این تابع توانایی‌های دیگری نیز دارد. یکی از مفید‌ترین آنها استفاده شدن به عنوان فضای نام گروهی برای توابع سودمند می‌باشد. تعداد زیادی تابع سودمند با استفاده از $ به عنوان فضای نام قابل دسترسی می‌باشند که اکثر نیازهای یک صفحه را پاسخگو می‌باشند در این پست برخی از آنها را معرفی می‌کنیم در پست‌های آینده سعی می‌کنیم توابع سودمند بیشتری را شرح دهیم.

فراخوانی و استفاده از این توابع در ابتدا ممکن است کمی عجیب به نطر برسد. به مثال زیر دقت کنید که تابع سودمند () trim را فراخوانی کرده ایم.
$.trim(someString);
 
در صورتی که نوشتن علامت $ برای شما عجیب به نطر می‌رسد می‌توانید شناسه دیگر با نام jQuery به کار ببرید. کد زیر دقیقا مانند بالا عمل می‌کند شاید درک آن راحت‌تر هم باشد.
jQuery.trim(someString);

بدیهی است که از  jQuery یا $ تنها به عنوان فضای نامی که تابع ()trim در آن تعریف شده اند، استفاده شده باشد.

نکته: اگر چه در نوشته‌های آنلاین jQuery، این عناصر به عنوان توابع سودمند در معرفی شده اند اما در حقیقت آنها متدهایی برای تابع ()$ می‌باشند.

عملکرد صفحه آماده (The document ready handler)
هنگامی که از Unobtrusive JavaScriptاستفاده می‌کنیم، رفتار از ساختار جدا می‌شود، بنابراین برای انجام عملیات روی عناصر صفحه باید منتظر بمانیم تا انها ایجاد شوند. برای رسیدن به این هدف، ما نیاز به راهی داریم که تا زمان ایجاد عناصر DOM روی صفحه منتظر بماند قبل از آن عملیات را اجرا کند.
به طور معمول از onload برای نمونه‌های window استفاده می‌شود، که پس از لود شدن کامل صفحه ، دستور‌ها قابل اجرا می‌باشند. بنابراین ساختار کلی آن کدی مانند زیر خواهد بود:
window.onload = function() {
    $("table tr:nth-child(even)").addClass("even");
};

نوشتن کد به صورت بالا سبب می‌شود که کد پس از بارگذاری کامل صفحه اجرا شود. متاسفانه، مرورگرها تا بعد از ساخته شدن عناصر صفحه صبر نمی‌کنند، بلکه پس از ساخت درخت عناصر صفحه منتظر بارگذاری کامل منابع خارجی صفحه مانند تصاویر نیز می‌مانند و سپس آنها را در پنجره مرورگر نمایش می‌دهند. در نتیجه بازدید کننده زمان زیادی منتظر می‌ماند تا رویداد onload تکمیل شود.
حتی بدتر از آن، زمانی است که اگر به طور مثال یکی از تصاویر با مشکل مواجه شود که زمان قابل توجهی صرف بارگذاری آن شود، کاربر باید تمام این مدت را صبر کند تا پس از آن بتواند با این صفحه کار کند. این نکته می‌تواند دلیلی برای استفاده نکردن از Unobtrusive JavaScriptبرای شروع کار باشد.
اما راه بهتری نیز وجود دارد، می‌توانیم تنها زمانی که قسمت ساختار عناصر صفحه ترجمه شده و HTML به درخت عناصر تبدیل می‌شود، صبر کنیم . پس از آن کد مربوط به رفتار‌ها را اجرا کنیم. رسیدن به این روش برای استفاده از Cross-Browser کمی مشکل است، اما به لطف jQuery و قدرت آن، این امر به سادگی امکان پذیر است و دیگر نیازی به منتظر ماندن برای بارگذاری منابع صفحه مانند تصاویر و ویدیوها نمی‌باشد. Syntax زیر نمونه ای از چنین حالتی است:
$(document).ready(function() {
   $("table tr:nth-child(even)").addClass("even");
});

ابتدا صفحه مورد نظر را به تابع ()$ ارسال کرده ایم، سپس هر زمان که آن صفحه آماده شد (Ready) ، تابع ارسال شده به آن اجرا خواهد شد. البته می‌توان کد نوشته شده بالا را به شکل مختصرتری هم نوشت:
$(function() {
    $("table tr:nth-child(even)").addClass("even");
});

با ارسال تابع به ()$، ما مرورگر را مجبور می‌کنیم که برای اجرای کد تا زمانی که DOM کامل لود شود (فقط DOM لود شود) منتظر بماند. حتی بهتر از آن ما می‌توانیم از این تکنیک چندین با در همان سند HTML استفاده کرده و مرورگر تمامی تابع‌های مشخص شده توسط ما را به ترتیب اجرا خواهد کرد. (یعنی من در دیک صفحه می‌توانم چنین بار تابع ()ready را فراخوانی کنم). در مقابل روش OnLoad پنجره فقط اجازه اجرای یکبار تابع را به ما می‌دهد.
این هم یکی دیگر از کارکردهای دیگر تابع ()$ می‌باشد. حال به یکی دیگر از امکاناتی که این تابع برای ما فراهم می‌کند دقت کنید.

ساختن اجزای DOM (ساختن عناصر صفحه)
یکی دیگر از کارهایی که تابع ()$ می تواند برای ما انجام دهد ایجاد کردن عناصر صفحه است. به این منظور ورودی تابع ()$ را یک رشته که حاوی دستور HTML مربوط به ساخت یک عنصر می‌باشد، قرار می‌دهیم. برای مثال دستور زیر یک تگ p ایجاد می‌کند:
$("<p>Hi there!</p>")

اما ایجاد یک عنصر DOM یا (سلسله مراتب عناصر DOM) برای ما به تنهایی سودمند نیست، و هدف ما چیز دیگری است. ایجاد اشیا صفحه توسط ()$ زمانی برای ما مفید خواهد بود که بخواهیم به هنگام ساخت، تابعی بروی آن اعمال کنیم یا به محض ساخت آن را به تابعی ارسال کنیم به کد زیر دقت کنید:
<html>
   <head>
        <title>Follow me!</title>
        <script type="text/javascript" src="../scripts/jquery-1.2.js"></script>
        <script type="text/javascript">
           // در زمان Reday بودن صفحه عنصر مورد نظر ایجاد می‌شود
           $(function(){
              $("<p>Hi there!</p>").insertAfter("#followMe");
           });
        </script>
    </head>
<body>
     <p id="followMe">Follow me!</p>
</body>
</html>

در کد بالا زمانی که صفحه مورد نظر Ready شد تابع مورد نظر ما اجرا شده و در عناصر صفحه بعد از عنصری که id آن followMe می‌باشد یک عنصر p را ایجاد می‌کند. که خروجی آن شبیه تصویر زیر خواهد بود.

مزیت دیگر jQuery این است که در صورتی که امکانی را ندارد شما به آسانی می‌توانید آن را توسعه داده و برای آن پلاگین طراحی کنید.

برای پایان دادن به این پست همانطور که دیدیم jQuery قادر به انجام کارهای زیر است:
  • انتخاب عناصر و ایجاد مجموعه ای از آنها که آماده اعمال متد‌های مختلف می‌باشند.
  • استفاده به عنوان یک فضای نام برای توابع سودمند.
  • ایجاد اشیا مختلف HTML بروی صفحه.
  • اجرای کد به محض آماده شدن اشیای صفحه.

موفق وموید باشید