نظرات مطالب
سفارشی سازی عناصر صفحات پویای افزودن و ویرایش رکوردهای jqGrid در ASP.NET MVC
- از formatterها برای سفارشی سازی و تغییر اطلاعات نهایی نمایش داده شده در یک سلول استفاده کنید.
- از callback ایی به نام beforeShowForm برای اضافه کردن عناصر سفارشی به فرم‌های ویرایش و افزودن رکوردها می‌شود استفاده کرد:
$.extend($.jgrid.edit, {
    bSubmit: "Save and Close",
    bCancel: "Cancel",
    width: 370,
    recreateForm: true,
    beforeShowForm: function () {
        $('<a href="#">Save and New<span class="ui-icon ui-icon-disk"></span></a>')
            .click(function() {
                alert("click!");
            }).addClass("fm-button ui-state-default ui-corner-all fm-button-icon-left")
              .prependTo("#Act_Buttons>td.EditButton");
    }
});
مطالب دوره‌ها
اجزای جاوا اسکریپتی بوت استرپ 3
مباحث منوهای پایین افتادنی و برگه‌‌ها (tabs) در بوت استرپ 2 و 3 یکسان هستند و نکات آن‌ها در بحث «نگاهی به اجزای تعاملی Twitter Bootstrap» عنوان شده‌اند. در ادامه به اجزای جدیدی خواهیم پرداخت که مختص بوت استرپ 3 بوده و از تازه‌های آن به‌شمار می‌روند. همچنین اجزایی را که در بررسی بوت استرپ 2 به آن‌ها پرداخته نشد، مانند Carousels و Scroll spy، نیز بررسی می‌کنیم.


پنل‌ها و آکاردئون‌ها

پنل‌های آکاردئونی، بسیار شبیه به برگه‌ها عمل می‌کنند. با کلیک بر روی یک سربرگ، محتوای مخفی شده آن نمایش داده می‌شود. این اعمال نیز توسط اجزای جاوا اسکریپتی بوت استرپ، به کمک jQuery عمل می‌کنند.
یک مثال:
    <div class="container">
        <h4 class="alert alert-info">
            پنل آکاردئونی</h4>
        <div class="row">
            <div class="panel-group" id="accordion">
                <div class="panel panel-default">
                    <div class="panel-heading">
                        <h4 class="panel-title">
                            <a href="#vacc" class="accordion-toggle" data-toggle="collapse" data-parent="#accordion">
                                <span class="glyphicon glyphicon-pushpin"></span>اطلاعات یک</a>
                        </h4>
                    </div>
                    <div id="vacc" class="panel-collapse collapse in">
                        <div class="panel-body">
                            <p>
                                متن متن متن متن .......</p>
                            <p>
                                <a href="#" class="btn btn-info">بیشتر >></a></p>
                        </div>
                    </div>
                </div>
                <div class="panel panel-default">
                    <div class="panel-heading">
                        <h4 class="panel-title">
                            <a href="#checkups" class="accordion-toggle" data-toggle="collapse" data-parent="#accordion">
                                <span class="glyphicon glyphicon-ok"></span>اطلاعات 2</a>
                        </h4>
                    </div>
                    <div id="checkups" class="panel-collapse collapse">
                        <div class="panel-body">
                            <p>
                                متن متن متن متن .......</p>
                            <p>
                                <a href="#" class="btn btn-info">بیشتر >></a></p>
                        </div>
                    </div>
                </div>
            </div>
            <!-- end accordion -->
        </div>
        <!-- end row -->
    </div>
    <!-- /container -->

توضیحات:
- ابتدا کل ناحیه مدنظر باید در یک div با کلاس panel-group محصور شود؛ به همراه یک id دلخواه. از این id در ویژگی‌های data-parent عنوان‌های هر پنل این گروه استفاده می‌شود. به این ترتیب سیستم جاوا اسکریپتی آن متوجه خواهد شد که باید داخل چه ناحیه‌ای از صفحه عمل کند.
- پس از مشخص سازی آغاز پنل گروهی مدنظر، هر گروه، داخل یک div با کلاس panel panel-default قرار خواهد گرفت. به این ترتیب اولین پنل آکاردئونی مثال، شکل می‌گیرد.
- سپس داخل هر پنل مجزا، باید توسط panel-heading مشخص کنیم که عنوان این پنل و محتوای خاص این عنوان کجا باید قرار گیرند. همچنین به کمک panel-collapse collapse in، محتوایی را که با کلیک بر روی عنوان هر پنل به صورت خودکار ظاهر خواهد شد را معرفی می‌کنیم.
در ادامه می‌توان پنل‌های بیشتری را به این مجموعه و گروه افزود.
- پنلی که قرار است در ابتدای کار باز باشد، دارای کلاس collapse in خواهد بود؛ مابقی فقط collapse دارند.


بررسی کامپوننت Carousels

بوت استرپ به همراه کامپوننت اسلایدشو توکاری است به نام Carousel که بدون نیاز به حتی یک سطر کدنویسی جاوا اسکریپت اضافی، یک اسلاید شو بسیار حرفه‌ای را ارائه می‌دهد. مثالی را در این مورد در ادامه ملاحظه می‌کنید:
    <div class="container">
        <h4 class="alert alert-info">
            اسلاید شو</h4>
        <div class="row">
            <div id="myCarousel" class="carousel slide">
                <ol class="carousel-indicators">
                    <li data-target="#myCarousel" data-slide-to="0" class="active"></li>
                    <li data-target="#myCarousel" data-slide-to="1"></li>
                    <li data-target="#myCarousel" data-slide-to="2"></li>
                </ol>
                <!-- carousel-indicators -->
                <section class="carousel-inner">
                    <div class="active item">
                        <img src="images/01.jpg" alt="Photo 1"></div>
                    <div class="item">
                        <img src="images/02.png" alt="Photo 2"></div>
                    <div class="item">
                        <img src="images/03.jpg" alt="Photo 3"></div>
                </section><!-- carousel-inner -->
                <a href="#myCarousel" class="left carousel-control" data-slide="prev"><span class="glyphicon glyphicon-chevron-left">
                </span></a><a href="#myCarousel" class="right carousel-control" data-slide="next"><span
                    class="glyphicon glyphicon-chevron-right"></span></a>
            </div>
            <!-- myCarousel -->
        </div>
        <!-- end row -->
    </div>




توضیحات:
- در قسمت carousel-inner این کامپوننت، لیست تک تک تصاویر مورد نیاز قرار خواهند گرفت. تصویر آغازین دارای div ایی محصور کننده با کلاس active item است و مابقی کلاس item دارند.
- مرحله بعد، کار افزودن سیستم راهبری و حرکت بین تصاویر اضافه شده است. این سیستم چیزی نیست جز چند لینک مزین شده با کلاس‌های left carousel-control و همچنین right carousel-control. ویژگی‌های data-slide این لینک‌ها نیز مشخص کننده اعمالی هستند که کامپوننت جاوا اسکریپتی carousel قرار است انجام دهد. برای مثال حرکت به قبل یا بعد. همچنین باید دقت داشت که href این لینک‌ها به id مرتبط با div اصلی دربرگیرنده این قسمت از صفحه اشاره می‌کند. از یک سری گلیف آیکن نیز برای نمایش فلش رو به چپ و راست نیز در اینجا استفاده شده است.
- قسمت لیست مرتبط دارای کلاس carousel-indicators، در حقیقت مشخص کننده سه دایره کوچکی است که در تصویر فوق ملاحظه می‌کنید. به ازای هر تصویر، یک مورد را باید افزود. در اینجا data-target هر آیتم به id مرتبط با div محصور کننده کل اسلایدشو اشاره می‌کند. data-slide-toها به شماره تصویر متناظر هر آیتم متصل خواهند شد. ایندکس آغازین این آیتم‌ها از صفر شروع می‌شود.



بررسی کامپوننت Scroll spy

اگر به مستندات رسمی بوت استرپ مراجعه کنید، منوی کنار صفحه با لغزش صفحه به سمت پایین ثابت است؛ اما به ازای هر سرفصل جدیدی در صفحه، آیتم فعال این منو نیز به صورت خودکار تغییر می‌کند. این قابلیت توسط کامپوننت Scroll spy ایجاد شده است. یک مثال:
<body id="articles" data-spy="scroll" data-target=".scrollspy">
    <div class="container">
        <h4 class="alert alert-info">
            Scroll spy</h4>
        <div id="articlesindex" class="row">
            <section class="scrollspy clearfix col col-lg-3 hidden-sm">
                <ul class="nav nav-list affix">
                    <li><a href="#item1"><span class="glyphicon glyphicon-user"></span>Item 1</a></li>
                    <li><a href="#item2"><span class="glyphicon glyphicon-user"></span>Item 2</a></li>
                    <li><a href="#item3"><span class="glyphicon glyphicon-user"></span>Item 3</a></li>
                </ul>
                <!-- nav-list -->
            </section><!-- scrollspy -->
            <section class="col col-lg-9">
                <article id="item1" class="media">
                    <h2>
                        item1</h2>
                    <div class="media-body">
                        <p>
                            متن متن متن ............متن متن متن ............متن متن متن .............</p>
                        <p>
                            متن متن متن ............متن متن متن ............متن متن متن .............</p>
                        <p>
                            متن متن متن ............متن متن متن ............متن متن متن .............</p>
                        <p>
                            متن متن متن ............متن متن متن ............متن متن متن .............</p>
                        <p>
                            متن متن متن ............متن متن متن ............متن متن متن .............</p>
                        <p>
                            متن متن متن ............متن متن متن ............متن متن متن .............</p>
                        <p>
                            متن متن متن ............متن متن متن ............متن متن متن .............</p>
                        <p>
                            متن متن متن ............متن متن متن ............متن متن متن .............</p>
                        <p>
                            متن متن متن ............متن متن متن ............متن متن متن .............</p>
                        <p>
                            متن متن متن ............متن متن متن ............متن متن متن .............</p>
                        <p>
                            متن متن متن ............متن متن متن ............متن متن متن .............</p>
                        <p>
                            متن متن متن ............متن متن متن ............متن متن متن .............</p>
                        <p>
                            متن متن متن ............متن متن متن ............متن متن متن .............</p>
                    </div>
                </article>
                <article id="item2" class="media">
                    <h2>
                        item2</h2>
                    <div class="media-body">
                        <p>
                            متن متن متن ............متن متن متن ............متن متن متن .............</p>
                        <p>
                            متن متن متن ............متن متن متن ............متن متن متن .............</p>
                        <p>
                            متن متن متن ............متن متن متن ............متن متن متن .............</p>
                        <p>
                            متن متن متن ............متن متن متن ............متن متن متن .............</p>
                        <p>
                            متن متن متن ............متن متن متن ............متن متن متن .............</p>
                        <p>
                            متن متن متن ............متن متن متن ............متن متن متن .............</p>
                        <p>
                            متن متن متن ............متن متن متن ............متن متن متن .............</p>
                        <p>
                            متن متن متن ............متن متن متن ............متن متن متن .............</p>
                        <p>
                            متن متن متن ............متن متن متن ............متن متن متن .............</p>
                        <p>
                            متن متن متن ............متن متن متن ............متن متن متن .............</p>
                        <p>
                            متن متن متن ............متن متن متن ............متن متن متن .............</p>
                        <p>
                            متن متن متن ............متن متن متن ............متن متن متن .............</p>
                        <p>
                            متن متن متن ............متن متن متن ............متن متن متن .............</p>
                    </div>
                </article>
                <article id="item3" class="media">
                    <h2>
                        item3</h2>
                    <div class="media-body">
                        <p>
                            متن متن متن ............متن متن متن ............متن متن متن .............</p>
                        <p>
                            متن متن متن ............متن متن متن ............متن متن متن .............</p>
                        <p>
                            متن متن متن ............متن متن متن ............متن متن متن .............</p>
                        <p>
                            متن متن متن ............متن متن متن ............متن متن متن .............</p>
                        <p>
                            متن متن متن ............متن متن متن ............متن متن متن .............</p>
                        <p>
                            متن متن متن ............متن متن متن ............متن متن متن .............</p>
                        <p>
                            متن متن متن ............متن متن متن ............متن متن متن .............</p>
                        <p>
                            متن متن متن ............متن متن متن ............متن متن متن .............</p>
                        <p>
                            متن متن متن ............متن متن متن ............متن متن متن .............</p>
                        <p>
                            متن متن متن ............متن متن متن ............متن متن متن .............</p>
                        <p>
                            متن متن متن ............متن متن متن ............متن متن متن .............</p>
                        <p>
                            متن متن متن ............متن متن متن ............متن متن متن .............</p>
                        <p>
                            متن متن متن ............متن متن متن ............متن متن متن .............</p>
                    </div>
                </article>
            </section><!-- artistinfo -->
        </div>
        <!-- end row -->
    </div>
    <!-- /container -->
    <script type="text/javascript" src="Scripts/jquery-1.10.2.min.js"></script>
    <script type="text/javascript" src="Scripts/bootstrap-rtl.js"></script>
</body>


توضیحات:
- کار با ایجاد یک section جدید که حاوی منوی ثابت کنار صفحه است، شروع می‌شود. این section دارای کلاس scrollspy می‌باشد. داخل این section لیست عناوین منو قرار می‌گیرند که ul آن دارای کلاس nav nav-list affix خواهد بود. affix سبب می‌شود تا این لیست در کنار صفحه ثابت نمایش داده شود و همواره نمایان باشد.
- نکته مهم لیست آیتم‌های منو، مقادیر href لینک‌های آن است. این مقادیر باید به id محتوای متناظر اشاره کنند. این محتواها را در ادامه‌ی کار ملاحظه می‌کنید.
- به علاوه اگر به تگ body در ابتدای کار دقت کرده باشید، ویژگی‌های data-spy و هدفی که قرار است تحت نظر قرار گیرد به آن اضافه شده‌است.
- تا اینجا این سیستم کار می‌کند؛ اما اگر صفحه را به بالا و پایین حرکت دهید، پس زمینه آیتم فعال، تغییر رنگ نمی‌دهد. برای این منظور نیاز است، به CSS سفارشی خود، چند سطر ذیل را اضافه کرد:
.scrollspy .nav > li.active{
   background: lightgray;
}


فایل‌های نهایی این قسمت را از اینجا نیز می‌توانید دریافت کنید:
bs3-sample04.zip
 
نظرات مطالب
ASP.NET MVC #8
من برای تمرین helper تگ img رو بصورت زیر پیاده سازی کردم :
 public static MvcHtmlString MyImage2(this HtmlHelper helper, string src, string alt, int width, int height)
  {
var img=new TagBuilder("img");
img.MergeAttribute("src", VirtualPathUtility.ToAbsolute(src));
img.MergeAttribute("alt", alt);
img.MergeAttribute("width", width.ToString());
img.MergeAttribute("height", height.ToString());
return MvcHtmlString.Create(img.ToString());
  }

آیا استفاده از VirtualPathUtility.ToAbsolute(src) برای بدست آوردن آدرس تصویر که در پوشه content قرار دارد روش درستی است ؟
من جواب گرفتم .ولی می‌خواستم بدون آیا میشه بهتر از این عمل کرد در کلاس helper
مطالب دوره‌ها
ارائه کاربری ساده‌تر انتخاب چندین آیتم از یک لیست به کمک افزونه TagIt در ASP.NET MVC
چندی قبل مطلبی را در مورد بررسی تفصیلی رابطه چند به چند در این سایت مطالعه کردید. در آن مطلب صرفا به بحث ذخیره سازی اطلاعات دریافتی از کاربر اشاره شد. برای مثال اگر مطلبی چندین برچسب دارد، چگونه باید این‌ها را در بانک اطلاعاتی به نحو صحیحی ذخیره کرد.
در مطلب جاری قصد داریم با نحوه ارائه یک UI کاربر پسند برای این منظور آشنا شویم و سؤال مهم هم این است: «چگونه می‌توان کار کاربر را در حین وارد کردن تعدادی از برچسب‌های مرتبط با یک مطلب ساده‌تر کرد؟». برای این منظور یکی از راه حل‌هایی که در بسیاری از سایت‌ها مرسوم شده است، استفاده از افزونه‌هایی مانند jQuery TagIt می‌باشد که در ادامه با نحوه استفاده از آن در ASP.NET MVC آشنا خواهیم شد.


پیشنیازها:
دریافت افزونه TagIt
همچنین دریافت jQuery UI (افزونه TagIt برای نمایش لیست Auto Complete آیتم‌ها از jQuery UI در پشت صحنه استفاده می‌کند)
<head>
    <title>@ViewBag.Title</title>
    <link href="@Url.Content("~/Content/TagIt/jquery-ui-1.8.23.custom.css")" rel="stylesheet" type="text/css" />
    <link href="@Url.Content("~/Content/TagIt/tagit-simple-blue.css")" rel="stylesheet" type="text/css" />
    <link href="@Url.Content("Content/Site.css")" rel="stylesheet" type="text/css" />
    <script src="@Url.Content("~/Scripts/jquery-1.9.1.min.js")" type="text/javascript"></script>
    <script src="@Url.Content("~/Scripts/jquery.validate.min.js")" type="text/javascript"></script>
    <script src="@Url.Content("~/Scripts/jquery.unobtrusive-ajax.min.js")" type="text/javascript"></script>
    <script src="@Url.Content("~/Scripts/jquery.validate.unobtrusive.min.js")" type="text/javascript"></script>
    <script src="@Url.Content("~/Content/TagIt/jquery-ui-1.8.23.custom.min.js")" type="text/javascript"></script>
    <script src="@Url.Content("~/Content/TagIt/tagit.js")" type="text/javascript"></script>
    @RenderSection("JavaScript", required: false)
</head>
که نهایتا نیاز است یک چنین تعاریفی را به فایل layout برنامه اضافه کنیم.

آشنایی با مدل برنامه

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

namespace jQueryMvcSample04.Models
{
    public class BlogPostViewModel
    {
        [DisplayName("عنوان"), Required(ErrorMessage = "*")]
        public string Title { set; get; }

        [DisplayName("متن"), Required(ErrorMessage = "*")]
        public string Body { set; get; }

        /// <summary>
        /// آرایه‌ای محدود از برچسب‌های این مطلب خاص به صورت جی‌سون که پیشتر ثبت شده است
        /// هدف استفاده در حین ویرایش مطلب
        /// </summary>
        public string InitialTags { set; get; }

        /// <summary>
        /// آرایه‌ای جی‌سونی از تمام برچسب‌های موجود در سیستم
        /// هدف نمایش منوی انتخاب برچسب‌ها از لیست
        /// </summary>
        public string TagsSource { set; get; }

        /// <summary>
        /// آرایه‌ای از برچسب‌های وارد شده توسط کاربر در حین ثبت مطلب
        /// </summary>
        [DisplayName("برچسب‌ها"), Required(ErrorMessage = "*")]
        public string[] Tags { set; get; }

        public int? Id { set; get; }
    }
}
اگر به نام این کلاس دقت کنید، به ViewModel ختم شده است. از این لحاظ که حاوی خواصی می‌باشد که عموما جهت رندر کردن صحیح UI مورد استفاده قرار می‌گیرند و معادلی در سمت بانک اطلاعاتی نخواهند داشت.
افزونه TagIt برای نمایش اطلاعات خود به دو منبع داده نیاز دارد:
الف) TagsSource : لیستی است به فرمت JSON، از هر آنچه که در سیستم پیشتر به عنوان یک برچسب ثبت شده است. از این لیست برای نمایش منوی خودکار انتخاب آیتم‌ها استفاده می‌شود.
ب) InitialTags : لیستی است به فرمت JSON، از تمام برچسب‌های مرتبط با یک مطلب. از این اطلاعات در حین ویرایش یک مطلب استفاده خواهد شد.

در این ViewModel یک خاصیت دیگر به شکل آرایه، به نام Tags تعریف شده است که لیست برچسب‌های وارد شده توسط کاربر را دریافت خواهد کرد.


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

using System.Web.Mvc;
using jQueryMvcSample04.Extensions;
using jQueryMvcSample04.Models;

namespace jQueryMvcSample04.Controllers
{
    public class HomeController : Controller
    {
        [HttpGet]
        public ActionResult Index(int? id)
        {
            //در ابتدای کار تمام تگ‌های موجود در سیستم از بانک اطلاعاتی دریافت خواهند شد
            //از این تگ‌ها برای تشکیل منوی انتخاب برچسب‌ها استفاده می‌شود
            var tagsSource = new[] { "C#", "C++", "C", "ASP.NET", "MVC" }.ToJson();

            //همچنین صرفا برچسب‌های مطلب جاری که پیشتر ثبت شده‌اند نیز باید از بانک اطلاعاتی دریافت گردند
            //از این برچسب‌ها برای ویرایش یک مطلب موجود استفاده خواهد شد
            var init = new[] { "ASP.NET" }.ToJson();

            var model = new BlogPostViewModel
            {
                TagsSource = tagsSource,
                InitialTags = init,
                Id = id
            };
            return View(model);
        }

        [HttpPost]
        public ActionResult Index(BlogPostViewModel data)
        {
            if (this.ModelState.IsValid)
            {
                //todo: save data
                // ...
                return RedirectToAction(actionName: "index", controllerName: "home");
            }

            //در صورت بروز خطا مجددا اطلاعات موجود نمایش داده خواهند شد
            data.TagsSource = new[] { "C#", "C++", "C", "ASP.NET", "MVC" }.ToJson();
            data.InitialTags = data.Tags.ToJson();
            return View(data);
        }
    }
}


با توجه به توضیحاتی که ارائه شد، کنترلر برنامه ساختار واضح‌تری را یافته است. در اولین بار نمایش صفحه، لیست منبع داده تگ‌ها و همچنین تگ‌های مرتبط با یک مطلب (در صورت وجود) به View ارائه خواهند شد.
از همین ViewModel، در عملیات Post نیز استفاده گردیده و اطلاعات دریافت می‌گردد.
تعریف متد الحاقی ToJson مورد استفاده را نیز در ادامه ملاحظه می‌نمائید:
using System.Linq;
using System.Web.Script.Serialization;

namespace jQueryMvcSample04.Extensions
{
    public static class JsonExt
    {
        public static string ToJson(this string[] initialTags)
        {            
            if (initialTags == null || !initialTags.Any())
                return "[]";
            else
                return new JavaScriptSerializer().Serialize(initialTags);
        }
    }
}

و مرحله آخر تعریف View متناظر است

@model jQueryMvcSample04.Models.BlogPostViewModel
@{
    ViewBag.Title = "Index";
}
@using (Html.BeginForm())
{
    @Html.ValidationSummary(true)

    <fieldset>
        <legend>ثبت مطلب جدید</legend>
        @Html.HiddenFor(model => model.Id)
        <div class="editor-label">
            @Html.LabelFor(model => model.Title)
        </div>
        <div class="editor-field">
            @Html.EditorFor(model => model.Title)
            @Html.ValidationMessageFor(model => model.Title)
        </div>
        <div class="editor-label">
            @Html.LabelFor(model => model.Body)
        </div>
        <div class="editor-field">
            @Html.EditorFor(model => model.Body)
            @Html.ValidationMessageFor(model => model.Body)
        </div>
        <div class="editor-label">
            @Html.LabelFor(model => model.Tags)
        </div>
        <div class="editor-field">
            <ul id="tagsList" dir="ltr" name="Tags">
            </ul>
            @Html.ValidationMessageFor(model => model.Tags)
        </div>
        <p>
            <input type="submit" value="Create" />
        </p>
    </fieldset>
}
@section JavaScript
{
    <script type="text/javascript">
    $(document).ready(function () {
            var tagsSource = @Html.Raw(Model.TagsSource);
            $('#tagsList').tagit({
                 tagSource: tagsSource, 
                 select: true, 
                 triggerKeys: ['enter', 'comma', 'tab'],
                 initialTags:  @Html.Raw(Model.InitialTags) 
              });
});
    </script>
}
در این View دو نکته حائز اهمیت هستند:
الف) برای نمایش افزونه TagIt از یک ul با id مساوی tagsList استفاده شده است.
ب) خواص اضافی موجود در ViewModel که اطلاعات JSON ایی مورد نیاز را بازگشت می‌دهند در قسمت اسکریپت صفحه مورد استفاده قرار گرفته‌اند. در اینجا نیاز است از Html.Raw استفاده شود تا اطلاعات مرتبط با JSON اشتباها encode نشده و قابل استفاده باشند.

دریافت مثال و پروژه کامل این قسمت
jQueryMvcSample04.zip
نظرات مطالب
ساختار پروژه های Angular
بخش اول سوال: بهتر است که کد مربوط به لود وابستگی‌ها در یک تابع مجزا نوشته شود و فقط در زمان نیاز این تابع را با پاس دادن وابستگی فراخوانی نمایید(با فرض اینکه نام این فایل dependencyResolver است):
(function()
{
    return function(dependencies)
    {
        var definition =
        {
            resolver: ['$q','$rootScope', function($q, $rootScope)
            {
                var deferred = $q.defer();

                $script(dependencies, function()
                {
                    $rootScope.$apply(function()
                    {
                        deferred.resolve();
                    });
                });

                return deferred.promise;
            }]
        }

        return definition;
    }
});
و برای لود وابستگی نیز تابع dependencyResolver را به این صورت فراخوانی نمایید:
angular.forEach(config.routes, function(route, path)
   {
                    $routeProvider.when(path, {templateUrl:route.templateUrl,   resolve:dependencyResolver(route.dependencies)});
    });
در مورد سوال دوم نیز باید عنوان کنم که شما می‌توانید مسیریابی هر ماژول را به صورت جداگانه در تعاریف همان ماژول‌ها انجام دهید که البته روشی مرسوم و معمول است. فقط در هنگام عملیات bootstrapping ماژول اصلی برنامه، سایر ماژول‌ها به عنوان وابستگی آن تعیین می‌شوند. به صورت زیر(عنوان ماژول‌ها را یکتا انتخاب نمایید) :
var app = angular.module('app', ['anotherModule1' , 'anotherModule2' , 'anotherModule3']);

مطالب
طراحی یک گرید با jQuery Ajax و ASP.NET MVC به همراه پیاده سازی عملیات CRUD

هدف، ارائه راه‌حلی برای نمایش جدولی اطلاعات، جستجو، مرتب سازی و صفحه بندی و همچنین انجام عملیات ثبت، ویرایش و حذف بر روی آنها به صورت Ajaxای در بخش back office نرم افزار می‌باشد.

پیش نیازها:

ایده کار به این شکل می‌باشد که برای نمایش اطلاعات به صورت جدولی با قابلیت‌های مذکور، لازم است یک اکشن Index برای نمایش اولیه و صفحه اول اطلاعات صفحه بندی شده و اکشن متدی به نام List برای پاسخ به درخواست‌های صفحه بندی، مرتب سازی، تغییر تعداد آیتم‌ها در هر صفحه و همچنین جستجو، داشته باشیم که این اکشن متد List، بعد از واکشی اطلاعات مورد نظر از منبع داده، آنها را به همراه اطلاعاتی که در کوئری استرینگ درخواست جاری وجود دارد در قالب یک PartialView به کلاینت ارسال کند.


ایجاد مدل‌های پایه

همانطور که در مقاله «طراحی و پیاده سازی ServiceLayer به همراه خودکارسازی Business Validationها» مطرح شد، برای پیاده سازی متدهای GetPagedList در ApplicationService‌ها از الگوی Request/Response استفاده می‌کنیم. برای این منظور واسط و کلاس‌های زیر را خواهیم داشت:

واسط IPagedQueryModel

    public interface IPagedQueryModel
    {
        int Page { get; set; }
        int PageSize { get; set; }

        /// <summary>
        ///     Expression of Sorting.
        /// </summary>
        /// <example>
        ///     Examples:
        ///     "Name_ASC"
        /// </example>
        string SortExpression { get; set; }
    }

این واسط قراردادی می‌باشد برای نوع و نام پارامترهایی که توسط کلاینت به سرور ارسال می‌شود. پراپرتی SortExpression آن، نام و ترتیب مرتب سازی را مشخص می‌کند؛ برای این منظور FieldName_ASC و FieldName_DESC به ترتیب برای حالات مرتب سازی صعودی و نزولی براساس FieldName مقدار دهی خواهد شد.

برای جلوگیری از تکرار این خصوصیات در مدل‌های کوئری مربوط به موجودیت‌ها، میتوان کلاس پایه‌ای به شکل زیر در نظر گرفت که پیاده ساز واسط بالا می‌باشد:

  public class PagedQueryModel : IPagedQueryModel, IShouldNormalize
    {
        public int Page { get; set; }
        public int PageSize { get; set; }

        /// <summary>
        ///     Expression of Sorting.
        /// </summary>
        /// <example>
        ///     Examples:
        ///     "Name_ASC"
        /// </example>
        public string SortExpression { get; set; }

        public virtual void Normalize()
        {
            if (Page < 1)
                Page = 1;

            if (PageSize < 1)
                PageSize = 10;

            if (SortExpression.IsEmpty())
                SortExpression = "Id_DESC";
        }
    }

مدل بالا علاوه بر پیاده سازی واسط IPagedQueryModel، پیاده ساز واسط IShouldNormalize نیز می‌باشد؛ دلیل وجود چنین واسطی در مقاله «طراحی و پیاده سازی ServiceLayer به همراه خودکارسازی Business Validationها» توضیح داده شده است:

پیاده سازی واسط IShouldNormalize باعث خواهد شد که قبل از اجرای خود متد، این نوع پارامترها با استفاده از یک Interceptor شناسایی شده و متد Normalize آنها اجرا شود.

کلاس PagedQueryResult

    public class PagedQueryResult<TModel>
    {
        public PagedQueryResult()
        {
            Items = new List<TModel>();
        }
        public IEnumerable<TModel> Items { get; set; }
        public long TotalCount { get; set; }
    }

دلیل وجود کلاس بالا در مقاله «طراحی یک گرید با Angular و ASP.NET Core - قسمت اول - پیاده سازی سمت سرور» توضیح داده شده است:

عموما ساختار اطلاعات صفحه بندی شده، شامل تعداد کل آیتم‌های تمام صفحات (خاصیت TotalItems) و تنها اطلاعات ردیف‌های صفحه‌ی جاری درخواستی (خاصیت Items) است و چون در اینجا این Items از هر نوعی می‌تواند باشد، بهتر است آن‌را جنریک تعریف کنیم.

کلاس PagedListModel

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

    public class PagedListModel<TModel>
    {
        public IPagedQueryModel Query { get; set; }

        public PagedQueryResult<TModel> Result { get; set; }
    }

پراپرتی Query در برگیرنده پارامتر ورودی اکشن متد List می‌باشد که پراپرتی‌های آن با مقادیر موجود در کوئری استرینگ درخواست جاری مقدار دهی شده‌اند؛ البته بدون وجود کلاس بالا نیز به کمک ViewBag می‌شود این اطلاعات ترکیبی را به ویو ارسال کرد که پیشنهاد نمی‌شود.


متد GetPagedListAsync موجود در CrudApplicationService

    public abstract class CrudApplicationService<TEntity, TModel, TCreateModel, TEditModel, TDeleteModel,
        TPagedQueryModel, TDynamicQueryModel> : ApplicationService,
        ICrudApplicationService<TModel, TCreateModel, TEditModel, TDeleteModel, TPagedQueryModel, TDynamicQueryModel>
        where TEntity : Entity, new()
        where TCreateModel : class
        where TEditModel : class, IModel
        where TModel : class, IModel
        where TDeleteModel : class, IModel
        where TPagedQueryModel : PagedQueryModel, new()
        where TDynamicQueryModel : DynamicQueryModel

    {

        #region Properties

        protected IQueryable<TEntity> UnTrackedEntitySet => EntitySet.AsNoTracking();
        public IUnitOfWork UnitOfWork { get; set; }
        public IMapper Mapper { get; set; }
        protected IDbSet<TEntity> EntitySet => UnitOfWork.Set<TEntity>();

        #endregion

        #region ICrudApplicationService Members

        #region Methods

        public virtual async Task<PagedQueryResult<TModel>> GetPagedListAsync(TPagedQueryModel model)
        {
            Guard.ArgumentNotNull(model, nameof(model));

            var query = ApplyFiltering(model);

            var totalCount = await query.LongCountAsync().ConfigureAwait(false);

            var result = query.ProjectTo<TModel>(Mapper.ConfigurationProvider);

            result = result.ApplySorting(model);
            result = result.ApplyPaging(model);

            return new PagedQueryResult<TModel>
            {
                Items = await result.ToListAsync().ConfigureAwait(false),
                TotalCount = totalCount
            };
        }
        #endregion

        #endregion

        #region Protected Methods

        /// <summary>
        ///     Apply Filtering To GetPagedList and GetPagedListAsync
        /// </summary>
        /// <param name="model"></param>
        /// <returns></returns>
        protected virtual IQueryable<TEntity> ApplyFiltering(TPagedQueryModel model)
        {
            Guard.ArgumentNotNull(model, nameof(model));

            return UnTrackedEntitySet;
        }
        #endregion
    }

در بدنه این متد، ابتدا عملیات جستجو توسط متد ApplyFiltering انجام می‌شود. این متد به صورت پیش فرض هیچ شرطی را بر روی کوئری ارسالی به منبع داده اعمال نمی‌کند؛ مگر اینکه توسط زیر کلاس‌ها بازنویسی شود و فیلترهای مورد نیاز اعمال شوند. سپس تعداد کل آیتم‌های فیلتر شده محاسبه شده و بعد از عملیات Projection، مرتب سازی و صفحه بندی انجام می‌گیرد. برای مباحث مرتب سازی و صفحه بندی از دو متد زیر کمک گرفته شده‌است:

    public static class QueryableExtensions
    {
        public static IQueryable<TModel> ApplySorting<TModel>(this IQueryable<TModel> query, IPagedQueryModel request)
        {
            Guard.ArgumentNotNull(request, nameof(request));
            Guard.ArgumentNotNull(query, nameof(query));

            return query.OrderBy(request.SortExpression.Replace('_', ' '));
        }

        public static IQueryable<TModel> ApplyPaging<TModel>(this IQueryable<TModel> query, IPagedQueryModel request)
        {
            Guard.ArgumentNotNull(request, nameof(request));
            Guard.ArgumentNotNull(query, nameof(query));

            return request != null
                ? query.Page((request.Page - 1) * request.PageSize, request.PageSize)
                : query;
        }
    }

به منظور مرتب سازی از کتابخانه  System.Liq.Dynamic کمک گرفته شده‌است.

نکته: مشخص است که این روش، وابستگی به وجود متد GetPagedListAsync ندارد و صرفا برای تشریح ارتباط مطالبی که قبلا منتشر شده بود، مطرح شد.


پیاده سازی اکشن متدهای Index و List

public partial class RolesController : BaseController
{
    #region Fields
        private readonly IRoleService _service;
        private readonly ILookupService _lookupService;

        #endregion

    #region Constractor
        public RolesController(IRoleService service,  ILookupService lookupService)
        {
            Guard.ArgumentNotNull(service, nameof(service));
            Guard.ArgumentNotNull(lookupService, nameof(lookupService));

            _service = service;
            _lookupService = lookupService;
        }
        #endregion

    #region Index / List
    [HttpGet]
    public virtual async Task<ActionResult> Index()
    {
        var query = new RolePagedQueryModel();
        var result = await _service.GetPagedListAsync(query).ConfigureAwait(false);

        var pagedList = new PagedListModel<RoleModel>
        {
            Query = query,
            Result = result
        };

        var model = new RoleIndexViewModel
        {
            PagedListModel = pagedList,
            Permissions = _lookupService.GetPermissions()
        };
        return View(model);
    }
    [HttpGet, AjaxOnly, NoOutputCache]
    public virtual async Task<ActionResult> List(RolePagedQueryModel query)
    {
        var result = await _service.GetPagedListAsync(query).ConfigureAwait(false);

        var model = new PagedListModel<RoleModel>
        {
            Query = query,
            Result = result
        };

        return PartialView(MVC.Administration.Roles.Views._List, model);
    }
    #endregion
}

به عنوان مثال در بالا کنترلر مربوط به گروه‌های کاربری را مشاهده می‌کنید. به دلیل اینکه علاوه بر مباحث صفحه بندی و مرتب سازی، امکان جستجو بر اساس نام و دسترسی‌های گروه کاربری را نیز نیاز داریم، لازم است مدل زیر را ایجاد کنیم:

    public class RolePagedQueryModel : PagedQueryModel
    {
        public string Name { get; set; }
        public string Permission { get; set; }
    }

در این مورد خاص لازم است لیست دسترسی‌های موجود درسیستم به صورت لیستی برای انتخاب در فرم جستجو مهیا باشد. فرم جستجو در ویو مربوط به اکشن Index قرار می‌گیرد و قرار نیست به همراه پارشال ویو List_ در هر درخواستی از سرور دریافت شود. لذا لازم است مدلی برای ویو Index در نظر بگیریم که به شکل زیر می‌باشد:

    public class RoleIndexViewModel
    {   
        public RoleIndexViewModel()
        {
            Permissions = new List<LookupItem>();
        }
        public IReadOnlyList<LookupItem> Permissions { get; set; }
        public PagedListModel<RoleModel> PagedListModel { get; set; }
    }

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


ویو Index.cshtml

@model RoleIndexViewModel

@{
    ViewBag.Title = L("Administration.Views.Role.Index.Title");
    ViewBag.ActiveMenu = AdministrationMenuNames.RoleManagement;
}

<div class="row">
    <div class="col-md-12">
        <div id="filterPanel" class="panel-collapse collapse" role="tabpanel" aria-labelledby="filterPanel">
            <div class="panel panel-default margin-bottom-5">

                <div class="panel-body">
                    @using (Ajax.BeginForm(MVC.Administration.Roles.List(),
new AjaxOptions { UpdateTargetId = "RolesList", HttpMethod = "GET" }, new { id = "filterForm", data_submit_on_reset = "true" }))
                    {
                        <div class="row">
                            <div class="col-md-3">
                                <input type="text" name="Name" class="form-control" value="" placeholder="@L("Administration.Role.Fields.Name")" />
                            </div>
                            <div class="col-md-3">
                                @Html.DropDownList("Permission", Model.Permissions.ToSelectListItems(), L("Administration.Views.Role.FilterBy.Permission"),new {@class="form-control"})
                            </div>
                            <div class="col-md-3">

                                <button type="submit"
                                        role="button"
                                        class="btn btn-info">
                                    @L("Commands.Filter")
                                </button>
                                <button type="reset"
                                        role="button"
                                        class="btn btn-default">
                                    <i class="fa fa-close"></i>
                                    @L("Commands.Reset")
                                </button>
                            </div>
                        </div>
                    }
                </div>
            </div>
        </div>
    </div>
</div>

<div class="row">
    <div class="col-md-12" id="RolesList">
        @{Html.RenderPartial(MVC.Administration.Roles.Views._List, Model.PagedListModel);}
    </div>
</div>

فرم جستجو باید دارای ویژگی data_submit_on_reset با مقدار "true" باشد. به منظور پاکسازی فرم جستجو و ارسال درخواست جستجو با فرمی خالی از داده، برای بازگشت به حالت اولیه از تکه کد زیر استفاده خواهد شد:

  $(document).on("reset", "form[data-submit-on-reset]",
            function () {
                var form = this;
                setTimeout(function () {
                    $(form).submit();
                });
            });

در ادامه پارشال ویو List_ با داده ارسالی به ویو Index، رندر شده و کار نمایش اولیه اطلاعات به صورت جدولی به اتمام می‌رسد.


پارشال ویو List.cshtml_

@model PagedListModel<RoleModel>
@{
    Layout = null;
    var rowNumber = (Model.Query.Page - 1) * Model.Query.PageSize + 1;
    var refreshUrl = Url.Action(MVC.Administration.Roles.List().AddRouteValues(new RouteValueDictionary(Model.Query.ToDictionary())));
}

<div class="panel panel-default margin-bottom-5">
    <table class="table table-bordered table-hover" id="RolesListTable" data-ajax-refresh-url="@refreshUrl" data-ajax-refresh-update="#RolesList">
        <thead>
            <tr>
                <th style="width: 5%;">
                    #
                </th>
                <th class="col-md-3 sortable">
                    @Html.SortableColumn("Name", L("Administration.Role.Fields.Name"), Model.Query, "RolesList", routeValueDictionary => Url.Action(MVC.Administration.Roles.List().AddRouteValues(routeValueDictionary)))
                </th>
                <th class="col-md-3 sortable">
                    @Html.SortableColumn("DisplayName", L("Administration.Role.Fields.DisplayName"), Model.Query, "RolesList", routeValueDictionary => Url.Action(MVC.Administration.Roles.List().AddRouteValues(routeValueDictionary)))
                </th>
                <th class="col-md-3 sortable">
                    @Html.SortableColumn("IsDefault", L("Administration.Role.Fields.IsDefault"), Model.Query, "RolesList", routeValueDictionary => Url.Action(MVC.Administration.Roles.List().AddRouteValues(routeValueDictionary)))
                </th>
               
                <th style="width: 5%;"></th>
            </tr>
        </thead>

        <tbody>
           @foreach (var role in Model.Result.Items)
            {
                <tr>
                    <td>@(rowNumber++.ToPersianNumbers())</td>
                    <td>@role.Name</td>
                    <td>@role.DisplayName</td>
                    <td class="text-center">@Html.DisplayFor(a => role.IsDefault)</td>
                    <td class="text-center operations">
                      
                        <div class="btn-group">

                            <span class="fa fa-ellipsis-h dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false"></span>
                            <ul class="dropdown-menu dropdown-menu-left">
                                <li>
                                    <a href="#"
                                       role="button"
                                       data-ajax="true"
                                       data-ajax-method="GET"
                                       data-ajax-update="#main-modal div.modal-content"
                                       data-ajax-url="@Url.Action(MVC.Administration.Roles.Edit(role.Id))"
                                       data-toggle="modal"
                                       data-target="#main-modal">
                                        <i class="fa fa-pencil"></i>
                                        @L("Commands.Edit")
                                    </a>
                                </li>
                                <li>
                                    <a href="#"
                                       role="button"
                                       id="delete-@role.Id"
                                       data-delete-url="@Url.Action(MVC.Administration.Roles.Delete())"
                                       data-delete-model='{"Id":"@role.Id","RowVersion":"@Convert.ToBase64String(role.RowVersion)"}'>
                                        <i class="fa fa-trash"></i>
                                        @L("Commands.Delete")
                                    </a>
                                </li>
                            </ul>
                        </div>
                    </td>
                </tr>
            }
        </tbody>
    </table>

</div>

<div class="row">
    <div class="col-md-8">
        @Html.Pager(Model, "RolesList", routeValueDictionary => Url.Action(MVC.Administration.Roles.List().AddRouteValues(routeValueDictionary)))
        @Html.PageSize("RolesList", Model.Query, routeValueDictionary => Url.Action(MVC.Administration.Roles.List().AddRouteValues(routeValueDictionary)), new { @class = "margin-right-5" }, "filterForm")
        @Html.Refresh("RolesList", Model.Query, routeValueDictionary => Url.Action(MVC.Administration.Roles.List().AddRouteValues(routeValueDictionary)), L("Commands.Refresh"))
    </div>
</div>

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

    var refreshUrl = Url.Action(MVC.Administration.Roles.List().AddRouteValues(new RouteValueDictionary(Model.Query.ToDictionary())));

refreshUrl برای ارسال درخواست به اکشن متد List در نظر گرفته شده‌است که در کوئری استرینگ مربوط به خود، اطلاعاتی (مرتب سازی، شماره صفحه، اطلاعات جستجو و همچنین تعداد آیتم‌های موجود در هر صفحه) را دارد که حالت فعلی گرید را می‌توانیم دوباره از سرور درخواست کنیم.

<table class="table table-bordered table-hover" id="RolesListTable" data-ajax-refresh-url="@refreshUrl" data-ajax-refresh-update="#RolesList">

دو ویژگی data-ajax-refresh-url و data-ajax-refresh-update برای جدولی که لازم است عملیات CRUD را پشتیبانی کند، لازم می‌باشد. در قسمت دوم به استفاده از این دو ویژگی در هنگام عملیات ثبت، ویرایش و حذف خواهیم پرداخت.

<th class="col-md-3 sortable">
    @Html.SortableColumn("Name", L("Administration.Role.Fields.Name"), Model.Query, "RolesList", routeValueDictionary => Url.Action(MVC.Administration.Roles.List().AddRouteValues(routeValueDictionary)))
</th>

ستونی که امکان مرتب سازی را دارد باید th آن، کلاس sortable را داشته باشد. همچنین باید از هلپری که پیاده سازی آن را در ادامه خواهیم دید، استفاده کنیم. این هلپر، نام فیلد، عنوان ستون، مدل Query و همچین یک urlFactory را در قالب یک ‎Func<RouteValueDictionary,string>‎ دریافت می‌کند.


پیاده سازی هلپر SortableColumn

        public static MvcHtmlString SortableColumn(this HtmlHelper html, string columnName,
            string columnDisplayName, IPagedQueryModel queryModel, string updateTargetId, Func<RouteValueDictionary, string> urlFactory)
        {
            var dictionary = queryModel.ToDictionary();

            var routeValueDictionary = new RouteValueDictionary(dictionary)
            {
                ["SortExpression"] = !queryModel.SortExpression.StartsWith(columnName)
                    ? $"{columnName}_DESC" : queryModel.SortExpression.EndsWith("DESC")
                        ? $"{columnName}_ASC" : queryModel.SortExpression.EndsWith("ASC")
                            ? string.Empty : $"{columnName}_DESC"
            };

            var url = urlFactory(routeValueDictionary);

            var aTag = new TagBuilder("a");
            aTag.Attributes.Add("href", "#");
            aTag.Attributes.Add("data-ajax", "true");
            aTag.Attributes.Add("data-ajax-method", "GET");
            aTag.Attributes.Add("data-ajax-update", updateTargetId.StartsWith("#") ? updateTargetId : $"#{updateTargetId}");
            aTag.Attributes.Add("data-ajax-url", url);
            aTag.InnerHtml = columnDisplayName;

            var iconCssClass = !queryModel.SortExpression.StartsWith(columnName)
                ? "fa-sort"
                : queryModel.SortExpression.EndsWith("DESC")
                    ? "fa-sort-down"
                    : "fa-sort-up";

            var iTag = new TagBuilder("i");
            iTag.AddCssClass($"fa {iconCssClass}");

            return new MvcHtmlString($"{aTag}\n{iTag}");
        }

ابتدا مدل Query با متد الحاقی زیر تبدیل به دیکشنری می‌شود. این کار از این جهت مهم است که پراپرتی‌های لیست موجود در مدل Query، لازم است به فرم خاصی به سرور ارسال شوند که در تکه کد زیر مشخص می‌باشد.

public static IDictionary<string, object> ToDictionary(this object source)
{
    return source.ToDictionary<object>();
}

public static IDictionary<string, T> ToDictionary<T>(this object source)
{
    if (source == null)
        throw new ArgumentNullException(nameof(source));

    var dictionary = new Dictionary<string, T>();

    foreach (PropertyDescriptor property in TypeDescriptor.GetProperties(source))
    {
        AddPropertyToDictionary(property, source, dictionary);
    }
    return dictionary;
}

private static void AddPropertyToDictionary<T>(PropertyDescriptor property, object source,
    IDictionary<string, T> dictionary)
{
    var value = property.GetValue(source);

    var items = value as IEnumerable;

    if (items != null && !(items is string))
    {
        var i = 0;
        foreach (var item in items)
        {
            dictionary.Add($"{property.Name}[{i++}]", (T)item);
        }
    }
    else if (value is T)
    {
        dictionary.Add(property.Name, (T)value);
    }

}

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

در ادامه پیاده سازی هلپر SortableColumn، از دیکشنری حاصل، یک وهله از RouteValueDictionary ساخته می‌شود. در زمان رندر شدن PartialView لازم است مشخص شود که برای دفعه بعدی که بر روی این ستون کلیک می‌شود، باید چه مقداری با پارامتر SortExpression موجود در کوئری استرینگ ارسال شود. از این جهت برای پشتیبانی ستون، از حالت‌های مرتب سازی صعودی، نزولی و برگشت به حالت اولیه بدون مرتب سازی، کد زیر را خواهیم داشت:

var routeValueDictionary = new RouteValueDictionary(dictionary)
{
    ["SortExpression"] = !queryModel.SortExpression.StartsWith(columnName)
        ? $"{columnName}_DESC" : queryModel.SortExpression.EndsWith("DESC")
            ? $"{columnName}_ASC" : queryModel.SortExpression.EndsWith("ASC")
                ? string.Empty : $"{columnName}_DESC"
};

در ادامه urlFactory با routeValueDictionary حاصل، Invoke می‌شود تا url نهایی برای مرتب سازی‌های بعدی را  از طریق یک لینک تزئین شده با data اتریبیوت‌های Unobtrusive Ajax در th مربوطه قرار دهیم.

برای مباحث صفحه بندی، بارگزاری مجدد و تغییر تعداد آیتم‌ها در هر صفحه، از سه هلپر زیر کمک خواهیم گرفت:

<div class="row">
    <div class="col-md-8">
        @Html.Pager(Model, "RolesList", routeValueDictionary => Url.Action(MVC.Administration.Roles.List().AddRouteValues(routeValueDictionary)))
        @Html.PageSize("RolesList", Model.Query, routeValueDictionary => Url.Action(MVC.Administration.Roles.List().AddRouteValues(routeValueDictionary)), new { @class = "margin-right-5" }, "filterForm")
        @Html.Refresh("RolesList", Model.Query, routeValueDictionary => Url.Action(MVC.Administration.Roles.List().AddRouteValues(routeValueDictionary)), L("Commands.Refresh"))
    </div>
</div>


پیاده سازی هلپر Pager

public static MvcHtmlString Pager<TModel>(this HtmlHelper html, PagedListModel<TModel> model,
        string updateTargetId, Func<RouteValueDictionary, string> urlFactory)
{
    return html.PagedListPager(
        new StaticPagedList<TModel>(model.Result.Items, model.Query.Page, model.Query.PageSize,
            (int)model.Result.TotalCount), page =>
       {
           var dictionary = model.Query.ToDictionary();
           var routeValueDictionary = new RouteValueDictionary(dictionary) { ["Page"] = page };
           return urlFactory(routeValueDictionary);
       }, PagedListRenderOptions.EnableUnobtrusiveAjaxReplacing(
            new PagedListRenderOptions
            {
                DisplayLinkToFirstPage = PagedListDisplayMode.Always,
                DisplayLinkToLastPage = PagedListDisplayMode.Always,
                DisplayLinkToPreviousPage = PagedListDisplayMode.Always,
                DisplayLinkToNextPage = PagedListDisplayMode.Always,
                MaximumPageNumbersToDisplay = 6,
                DisplayItemSliceAndTotal = true,
                DisplayEllipsesWhenNotShowingAllPageNumbers = true,
                ItemSliceAndTotalFormat = $"تعداد کل: {model.Result.TotalCount.ToPersianNumbers()}",
                FunctionToDisplayEachPageNumber = page => page.ToPersianNumbers(),
            },
            new AjaxOptions
            {
                AllowCache = false,
                HttpMethod = "GET",
                InsertionMode = InsertionMode.Replace,
                UpdateTargetId = updateTargetId
            }));
}

در متد بالا از کتابخانه PagedList.Mvc استفاده شده‌است. یکی از overload‌های متد PagedListPager آن، یک پارامتر از نوع Func<int, string>‎ به نام generatePageUrl را دریافت می‌کند که امکان شخصی سازی فرآیند تولید لینک به صفحات بعدی و قبلی را به ما می‌دهد. ما نیز از این امکان برای افزودن اطلاعات موجود در مدل Query، به کوئری استرینگ لینک‌های تولیدی استفاده کردیم و صرفا برای لینک‌های ایجادی لازم بود مقادیر پارامتر Page موجود در کوئری استرینگ تغییر کند که در کد بالا مشخص می‌باشد.


پیاده سازی هلپر PageSize

public static MvcHtmlString PageSize(this HtmlHelper html, string updateTargetId, IPagedQueryModel queryModel, Func<RouteValueDictionary, string> urlFactory, object htmlAttributes = null, string filterFormId = null, params int[] numbers)
{
    if (numbers.Length == 0)
        numbers = new[] { 10, 20, 30, 50, 100 };

    var dictionary = queryModel.ToDictionary();

    var routeValueDictionary = new RouteValueDictionary(dictionary)
    {
        [nameof(IPagedQueryModel.Page)] = 1
    };
    routeValueDictionary.Remove(nameof(IPagedQueryModel.PageSize));

    var url = urlFactory(routeValueDictionary);

    var formTag = new TagBuilder("form");
    formTag.Attributes.Add("action", url);
    formTag.Attributes.Add("method", "GET");
    formTag.Attributes.Add("data-ajax", "true");
    formTag.Attributes.Add("data-ajax-method", "GET");
    formTag.Attributes.Add("data-ajax-update", updateTargetId.StartsWith("#") ? updateTargetId : $"#{updateTargetId}");
    formTag.Attributes.Add("data-ajax-url", url);

    if (htmlAttributes != null)
        formTag.MergeAttributes(HtmlHelper.AnonymousObjectToHtmlAttributes(htmlAttributes));

    formTag.AddCssClass("form-inline inline");

    var items = numbers.Select(number =>
        new SelectListItem
        {
            Value = number.ToString(),
            Text = number.ToString().ToPersianNumbers(),
            Selected = queryModel.PageSize == number
        });

    formTag.InnerHtml = html.DropDownList(nameof(IPagedQueryModel.PageSize), items, new { @class = "form-control page-size", onchange = "$(this.form).submit();" }).ToString();

    if (filterFormId.IsEmpty()) return new MvcHtmlString($"{formTag}");

    // ReSharper disable once MustUseReturnValue
    var scriptBlock = $"<script type=\"text/javascript\"> if(window.jQuery){{$('form#{filterFormId}').find('input[name=\"{nameof(IPagedQueryModel.PageSize)}\"]').remove();\n $('form#{filterFormId}').append(\"<input type='hidden' name='{nameof(IPagedQueryModel.PageSize)}' value='{queryModel.PageSize}'/>\")}}</script>";

    return new MvcHtmlString($"{formTag}\n{scriptBlock}");
}

ایده کار به این صورت است که یک المنت select، درون یک المنت form قرار می‌گیرد و در زمان change آن، فرم مربوطه submit می‌شود.

    formTag.InnerHtml = html.DropDownList(nameof(IPagedQueryModel.PageSize), items, new { @class = "form-control page-size", onchange = "$(this.form).submit();" }).ToString();

در زمان تغییر تعداد نمایشی آیتم‌ها در هر صفحه، لازم است حالت فعلی گرید حفظ شود و صرفا پارامتر Page ریست شود.


نکته مهم: در این طراحی اگر فرم جستجویی دارید، در زمان جستجو هیچیک از پارامتر‌های مربوط به صفحه بندی و مرتب سازی به سرور ارسال نخواهند شد (در واقع ریست می‌شوند) و کافیست یک درخواست GET معمولی با ارسال محتویات فرم به سرور صورت گیرد؛ ولی لازم است PageSize تنظیم شده، در زمان اعمال فیلتر نیز به سرور ارسال شود. از این جهت اسکریپتی برای ایجاد یک input مخفی در فرم جستجو نیز هنگام رندر شدن PartialView در صفحه تزریق می‌شود.

  var scriptBlock = $"<script type=\"text/javascript\"> if(window.jQuery){{$('form#{filterFormId}').find('input[name=\"{nameof(IPagedQueryModel.PageSize)}\"]').remove();\n $('form#{filterFormId}').append(\"<input type='hidden' name='{nameof(IPagedQueryModel.PageSize)}' value='{queryModel.PageSize}'/>\")}}</script>";


پیاده سازی هلپر Refresh

public static MvcHtmlString Refresh(this HtmlHelper html, string updateTargetId, IPagedQueryModel queryModel,
    Func<RouteValueDictionary, string> urlFactory, string label = null)
{
    var dictionary = queryModel.ToDictionary();

    var routeValueDictionary = new RouteValueDictionary(dictionary);

    var url = urlFactory(routeValueDictionary);

    var aTag = new TagBuilder("a");
    aTag.Attributes.Add("href", "#");
    aTag.Attributes.Add("role", "button");
    aTag.Attributes.Add("data-ajax", "true");
    aTag.Attributes.Add("data-ajax-method", "GET");
    aTag.Attributes.Add("data-ajax-update", updateTargetId.StartsWith("#") ? updateTargetId : $"#{updateTargetId}");
    aTag.Attributes.Add("data-ajax-url", url);
    aTag.AddCssClass("btn btn-default");

    var iTag = new TagBuilder("i");
    iTag.AddCssClass("fa fa-refresh");

    aTag.InnerHtml = $"{iTag} {label}";

    return new MvcHtmlString(aTag.ToString());
}

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


روش دیگر برای مدیریت این چنین کارهایی، استفاده از یک المنت form و قرادادن کل گرید به همراه یک سری input مخفی معادل با پارامترهای دریافتی اکشن متد List و مقدار دهی آنها در زمان کلیک بر روی دکمه‌های صفحه بندی، بارگزاری مجدد، دکمه اعمال فیلتر و لیست آبشاری تنظیم تعداد آیتم‌ها، درون آن نیز میتواند کار ساز باشد؛ اما در زمان پیاده سازی خواهید دید که پیاده سازی آن خیلی سرراست، به مانند پیاده سازی موجود در مطلب جاری نخواهد بود. 

در قسمت دوم، به پیاده سازی عملیات ثبت، ویرایش و حذف برپایه مودال‌های بوت استرپ و افزونه Unobtrusive Ajax خواهیم پرداخت.
کدهای کامل قسمت جاری، بعد از انتشار قسمت دوم، در مخزن گیت هاب شخصی قرار خواهد گرفت.

مطالب
آشنایی با WPF قسمت ششم : DataContext بخش سوم
در قسمت قبلی با مبدل‌ها آشنا شدیم و با استفاده از این ویژگی، دو کنترل Radio Button و CheckBox را بایند کردیم. الان تنها دو کنترل مانده تا آن‌ها را متصل کنیم؛ کنترل ListBox و تقویم، که در این قسمت لیست را بررسی می‌کنیم.

ListBox
در مورد لیست، ما قبلا نام کشورها را با استفاده از تگ ListBoxItem به طور دستی اضافه می‌کردیم و هر گونه ویرایش و اضافه کردن عکس و دیگر اشیاء را داخل این تگ برای هر آیتم جداگانه انجام می‌دادیم؛ مثل تصویر زیر که هر آیتم شامل یک تگ تصویر و دو تگ TextBlock است که یکی از آن‌ها رنگی شده است. کد هر آیتم به طور جداگانه و دستی اضافه شده است.


 ولی در روش بایندینگ چنین چیزی ممکن نیست و تنها با استفاده از یک Template موارد بالا را ایجاد می‌کنیم. پس محتویات سابق ListBox را حذف کرده و تگهای زیر را جهت افزودن یک قالب داده Data Template به شیء لیست اضافه می‌کنیم. حال اگر داده‌های لیست شده خود را روانه  DataContext کنید باید این اطلاعات نمایش داده شوند.
 <ListBox Grid.Row="3" Name="MyListBox" Grid.Column="1" Margin="10"  Height="80" >
               <ListBox.ItemTemplate>
                    <DataTemplate>
                        <WrapPanel>
                            <Image Width="24" Height="24" Source="{Binding Flag}"></Image>
                            <TextBlock Padding="5 5 0 0" Text="{Binding Name}"></TextBlock>
                        </WrapPanel>
                    </DataTemplate>
               </ListBox.ItemTemplate>
            </ListBox>
در برنامه ما مشکلی که هست، کد بالا جهت اتصال به DataContext ای است که قبلا پر شده است (DataContext کل View اصلی یا والد تمامی اشیاء مشتق از آن). حتما به یاد دارید که ما این شیء را با مدل یک رکورد ذخیره شده (مدل Person) در منبع داده‌ها پر کرده بودیم. پس استفاده از این روش در حال حاضر منتفی است. ممکن است شما در طول ساخت یک پنجره چندین و چند جا نیاز به منابع داده مختلفی داشته باشید ولی عموما DataContext با یک مدل جهت نمایش یا ذخیره یک رکورد بایند شده است. پس چکار کنیم؟

ارائه این نکته ضروری است که همه اشیاء خصوصیت DataContext را دارند و ما در مثال قبلی DataContext ریشه یا والد اشیاء را پر کردیم. اگر مقاله "ساختار سلسله مراتبی " را به یاد بیاورید، گفتیم که هر شیء در صورتیکه خصوصیت وابسته‌ای برایش تعریف نشده باشد، به سمت اشیاء والد حرکت می‌کند، به این جهت بود که همه‌ی کنترل‌ها به منبع داده‌ها دسترسی داشتند. پس ما اگر DataContext لیست را پر کنیم، لیست دلیلی برای دسترسی به DataContext اشیاء والد ندارد و خصوصیت پر شده‌ی خودش را در نظر می‌گیرد. پس بیایید این مورد را امتحان کنیم:
من کلاس زیر را جهت ارسال لیستی از کشورها به همراه آدرس پرچمشان، بر می‌گردانم:
دلیل استفاده از کلاس ObservableCollection در کد زیر به جای استفاده از اشیایی چون Ilist و ... این بود که این کلاس به اینترفیس هایی چون INotifyPropertyChanged مزین گشته و هر گونه تغییری در این مجموعه، از قبیل حذف و اضافه را اطلاع رسانی کرده و مدل تغییر یافته را به سمت ویو هدایت می‌کند.
using System.Collections.ObjectModel;

namespace test
{
    public class Country
    {
        public string Flag {
            get { return "Images/flags/" + Name + ".png"; }
        }
        public string Name { get; set; }

        public int Id { get; set; }

        public ObservableCollection<Country> GetCountries()
        {
            var countries = new ObservableCollection<Country>();
            countries.Add(new Country(){Id =1,Name = "Afghanistan"});
            countries.Add(new Country() { Id = 2, Name = "Albania" });
            countries.Add(new Country() { Id = 3, Name = "Angola" });

            countries.Add(new Country() { Id = 4, Name = "Bahrain" });
            countries.Add(new Country() { Id = 5, Name = "Bermuda" });
            countries.Add(new Country() { Id =6, Name = "Iran" });

            return countries;
        }
    }
}
برنامه را اجرا کرده و انتظار داریم که بتوانیم لیست پر شده‌ای از داده‌ها را ببینیم؛ ولی در کمال تعجب لیست خالی است. خطایی هم برگردانده نمی‌شود.

دلیل این مشکل این است که DataContext برای نمایش یک Object تهیه شده است و در مورد داده‌های لیستی باید از خصوصیتی به نام ItemsSource استفاده کرد که برای داده‌های لیستی IEnumerables، بهینه شده است.
پس به این ترتیب می‌نویسیم :
   public MainWindow()
        {
            InitializeComponent();
            person = Person.GetPerson();
            DataContext = person;

            //خط جدید
            MyListBox.ItemsSource = new Country().GetCountries();
        }
حال برنامه را اجرا کرده تا نتیجه را مشاهده کنید.

شکل‌های زیر یک نمودار از ارتباط با Object برای واکشی داده هاست:

شکل زیر همان نمودار بالا را ترسیم میکند ولی دیگر از مبدل پیش فرض WPF خبری نیست و مبدل اختصاصی به اسم ColorBrush جایگزین آن شده است:

نمودار زیر هم دسترسی به مجموعه ای از داده‌های لیستی است که از طریق ItemsSource خوانده می‌شوند:

کد زیر همچنین برای اتصال به کار می‌رود:
        public MainWindow()
        {
            InitializeComponent();
            person = Person.GetPerson();
            DataContext = person;

            //خط جدید
            MyListBox.DataContext = new Country().GetCountries();
            MyListBox.SetBinding(ItemsControl.ItemsSourceProperty, new Binding());
        }
روش بالا اتصال را برقرار می‌کند ولی من توصیه چندانی در استفاده از آن نمی‌کنم. آزاد گذاشتن DataContext یک لیست، یک مزیت هم دارد و آن این است که خارج از تگ Item‌ها یعنی همان تگ لیست، موقعی که  از بایندینگ استفاده می‌کنید، در واقع از DataContext کمک گرفته می‌شود؛ چون خود ListBox یک آیتم نیست که بخواهد با آیتمی در یک لیست سر و کله بزند. بلکه می‌تواند به راحتی به یک شیء، خود را بایند کند؛ مثال زیر نمونه‌ای از آن است.

پی نوشت : روش‌های دیگر بایند کردن همچون استفاده از منابع یا ریسورس‌ها یا استفاده از ViewModel‌ها هم هستند که در آینده در مورد آن‌ها بیشتر صحبت خواهیم کرد.

حال که توانستیم لیست را پر کنیم باید کشوری را که در رکورد واکشی شده آمده است، در لیست انتخاب کنیم.
توجه داشته باشید که باید لیست را از طریق خصوصیت ItemsSource پر کرده باشید و DataContext را دستکاری نکرده باشید.
خصوصیت Country در کلاس Person می‌تواند به دو صورت زیر باشد:
 public int Country { get; set; }
 public Country Country { get; set; }

که در هر دو حال از خصوصیت SelectedValue شی ListBox استفاده می‌شود. هر دو خط زیر به ترتیب برای استفاده از مقادیر بالا به کار می‌روند:
<ListBox Grid.Row="3" Name="MyListBox" Grid.Column="1" Margin="10"  Height="80" SelectedValuePath="Id" SelectedValue="{Binding Country}"  >               
<ListBox Grid.Row="3" Name="MyListBox" Grid.Column="1" Margin="10"  Height="80" SelectedValuePath="Id" SelectedValue="{Binding Country.Id}"  >
خصوصیت SelectedValuePath برای مشخص کردن اینکه کدام فیلد را باید در آیتم‌های لیست، جست و جو کند به کار می‌رود که ما در اینجا فیلد Id را که در کلاس Country قرار دارد، معرفی کرده‌ایم.
خصوصیت‌های دیگر یک شیء لیستی چون ListBox و ComboBox و ... SelectedIndex است که اندیس یک آیتم انتخابی را بازگردانده یا جهت انتخاب یک آیتم، اندیس آن را دریافت می‌کند. SelectedItem و SelectedItems هم شیء یا شیء‌هایی از مدل را (در اینجا Country) که در لیست انتخاب شده‌اند، بر می‌گرداند (فقط خواندنی).
 نتیجه اینکه اگر روش بالا با دستکاری DataContext انجام می‌گرفت دیگر استفاده از فیلد Country در مدل Peron ممکن نبود.
مطالب
استخراج داده های وب سرویس توسط SSIS
یکی از راه‌های انتقال اطلاعات بین زیر سیستم ها، استفاده از وب سرویس‌ها است . در این پست نحوه استخراج اطلاعات از وب سرویس و تبدیل آنها به یک فایل XML را به کمک package‌های SSIS توضیح می‌دهم . 
در قدم اول نیاز به یک سرویس داریم که برای نمونه سرویس زیر را طراحی و پیاده سازی می‌کنیم :
متدی به نام HelloWorld در کلاس GetUserInfo که خروجی آن آرایه ای از کلاس UserInfo است. 


و اجرای سرویس
 


و ساختار سرویس که SSIS به این ساختار نیاز دارد .

 

حال از محیط BIDS یک پروژه SSIS ایجاد می‌کنیم (برای آشنایی با BIDS به این پست مراجعه کنید) و یک Web Service Task به آن اضافه می‌کنیم :

 


سپس روی task دوبار کلیک کنید تا پنجره تنظیمات آن باز شود :

 


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


دکمه تست را فشار دهید تا تاییدیه معتبر بودن سرویس را دریافت کنید ، OK را فشار دهید .

در قدم بعد SSIS به قالب فایل xml که در خروجی WSDL است نیاز دارد . اگر این فایل را دارید آدرس آن را باید در قسمت WSDL FIle وارد کنید در غیر این صورت (مانند شرایط فعلی این مثال) باید این فایل را از سرویس خود دانلود کنید . نکته اینکه در صورتی که این فایل را ندارید ، گزینه overwriteWSDLFile را True کنید و مسیر یک فایلی که در سیستم شما موجود نیست را در آدرس WSDLFile وارد کنید . 



حال شما فایل مورد نظر را دارید :

 

اگر به تب input بازگردید می‌توانید ادامه تنظیمات را انجام دهید :

 


حال باید خروجی مورد نظر از این سرویس را تعریف کنیم . دو نوع خروجی برای ما امکان پذیر است . یکی انتقال اطلاعات به یک فایل (مناسب برای مواردی که نیاز به داده‌های offline دارید) و یکی منتقل کردن آنها به متغیر‌های خود SSIS Package برای استفاده در کام‌های بعدی flow (برای مواردی که نیاز به انجام تغییرات روی داده‌های Online دارید) .

 

پس از انجام این تنظیمات باید کانکشن هایی مطابق زیر داشته باشید :

 

F5 را فشار دهید تا عملیات شروع شود :

 و خروجی :

نظرات مطالب
AngularJS #2
سلام
بنده طبق فرمایشات شما به روش زیر عمل کردم ولی بهم کار نمیده و پارشال مورد نظر را به من نشان نمیده. میشه بگید مشکل از کجاست؟
public class PostController : Controller
    {
        ApplicationDbContext db;
        // GET: Post
        public ActionResult List()
        {
            using (db=new ApplicationDbContext())
            {
                var query = db.Posts.ToList();
                return PartialView("List",query);
            }
        }
    }

و کدهای ویو:
<div ng-app="postmodule">

    <div ng-controller="PostController">
        <ul>
            <li ng-repeat="item in ListOfItems">
                {{item.Title}}
                <hr />
                {{item.Text}}
            </li>
        </ul>
    </div>

</div>

و کدهایی که در فایل _Layout.cshtml نوشته ام:
    <div class="container body-content" >

        <script src="~/Scripts/angular.js"></script>
        <script src="~/Scripts/angular-route.js"></script>
        <script>
            var PostApp = angular.module('postmodule', []).config(['$routeProvider',
  function ($routeProvider) {
      $routeProvider.
          when('/list', {
              templateUrl: '/Post/List',
              controller: 'PostController'
          });
  }]);
            PostApp.controller('PostController', function ($scope, $http, postServices) {
                //...
                $scope.ListOfItems =
                              postServices.GetPosts()
                                  .success(function (data) {
                                      $scope.ListOfItems = data;
                                  });
                //...
            });
            PostApp.service('postServices', function ($http) {
                this.GetPosts = function () {
                    return $http.get('/Post/List');
                };
            });
        </script>

        <a href="#list">list post</a>
        <div ng-view=""></div>

        @RenderBody()
        <hr />
        <footer>
            <p>&copy; @DateTime.Now.Year - My ASP.NET Application</p>
        </footer>
    </div>
مطالب
برنامه نویسی پیشرفته JavaScript - قسمت 5 - معرفی برخی عملگرها

معرفی برخی عملگرها

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


عملگر typeof

از آنجائیکه جاوا اسکریپت دارای نوع داده‌ای ضعیف یا Loosely Typed می‌باشد، باید در بکارگیری متغیرها و یا آرگومانهای ورودی توابع، دقت لازم را داشته باشیم تا خطایی در اجرای کد یا محاسبات به وجود نیاید. بنابراین به راهکارهایی نیاز داریم تا بتوانیم نوع داده‌ای یک متغیر را تشخیص دهیم و قبل از بکارگیری آنها صحت و اعتبار داده‌های ورودی را بررسی کنیم. با استفاده از عملگر typeof می‌توانیم نوع داده‌ای یک متغیر را تشخیص دهیم که برای هر نوع داده‌ای مقادیر زیر را بر میگرداند:

· برای متغیرهایی که شامل مقدار undefined می‌باشند مقدار "undefined"

· برای متغیرهای منطقی یا Boolean مقدار "boolean"

· برای متغیرهای رشته‌ای یا String مقدار "string"

· برای متغیرهای عددی و مقادیر NaN و Infinity مقدار "number"

· برای تابع مقدار "function"

· برای اشیا و مقادیر null مقدار "object"

var x;
var n = 12;
var obj = {};
var fn = function () { };
var a = new Array();

alert(typeof x);        // "undefined"
alert(typeof n);        // "number"
alert(typeof obj);     // "object"
alert(typeof fn);       // "function"
alert(typeof a);        // "object"

عملگر instanceof

عملگر typeof بهترین روش جهت تشخیص نوع داده‌ای متغیرهایی است که دارای نوع داده‌ای پایه یا Primitive Type هستند. اما جهت تشخیص نوع داده‌ای اشیاء و به صورت کلی انواع ارجاعی، این عملگر فقط مقدار "object" را برمیگرداند و اشاره‌ای به ماهیت واقعی آن Object ندارد. برای این منظور می‌توانیم از عملگر instanceof استفاده نماییم تا بررسی کنیم یک نوع ارجاعی از جنس چه نوع Object ی می‌باشد. شکل کلی استفاده از این عملگر به صورت زیر است:

result = variable instanceof constructor

اگر variable ، از جنس نوع ارجاعی تعیین شده در بخش سازنده یا constructor باشد، عملگر instanceof مقدار true را بر می‌گرداند. به مثال زیر توجه کنید:

var a = new Array();
alert(a instanceof Array); // true
alert(a instanceof Object);   // true
alert(a instanceof Date); // false
توجه داشته باشید که اگر عملگر instanceof برای یک نوع ارجاعی به کار رود و با سازنده Object بررسی شود، همیشه مقدار true برمی گرداند.

عملگر in

همانطور که قبلا اشاره شد، جهت دسترسی به اعضای یک شیء، می‌توان با آن شیء همانند یک آرایه رفتار نمود. به عبارتی دیگر میتوان نام یک ویژگی یا تابع را در [] قرار داد تا به مقدار آن دسترسی داشت. بنابراین می‌توان همانند یک آرایه و با استفاده از یک حلقه‌ی for-in تمامی اعضای یک شیء را پیمایش نمود. در واقع عملگر in در این حلقه بررسی می‌کند چه ویژگی‌ها و توابعی در یک شیء وجود دارند و تمامی آنها را بر می‌گرداند. به مثال زیر توجه کنید:

var person = {
    name: "Meysam",
    age: 33,
    sayInfo: function () {
        alert(name + ":" + age);
    }
};

for (var i in person) 
    alert(i + " => " + person[i]);

خروجی :

     name => Meysam

    age => 33

    sayInfo => function() {
        alert(name + ":" + age);
    }
در مثال فوق، توسط حلقه‌ی for-in ، شیء person را پیمایش نمودیم. در این پیمایش، متغیر i ، به تک تک اعضای موجود در این شیء اشاره می‌کند. بنابراین متغیر i شامل نام ویژگی یا تابع می‌باشد و person[i] مقدار موجود در آن ویژگی یا محتوای تابع را بر میگرداند.

کاربرد دیگر عملگر in بررسی وجود یک ویژگی یا تابع در یک شیء می‌باشد. اگر ویژگی یا تابع مورد نظر در شیء وجود داشته باشد، مقدار true را  بر می‌گرداند. به مثال زیر توجه کنید:

alert("name" in person); // true
alert("sayInfo" in person); // true
alert("birth" in person); // false


عملگر delete

از عملگر delete جهت حذف یک ویژگی و یا یک تابع از یک شیء استفاده می‌شود. به مثال زیر توجه کنید:

var person = {
    name: "Meysam",
    age: 33,
    sayInfo: function () {
        alert(name + ":" + age);
    }
};

alert("sayInfo" in person); // true
delete person.sayInfo;
alert("sayInfo" in person); // false
در مثال فوق پس از به کارگیری عملگر delete ، تابع sayInfo از شیء person حذف شده است. بنابراین در آخرین alert اعلام می‌کند که شیء person دیگر شامل این تابع نمی‌باشد.


ویژگی constructor

پس از عملگرهای فوق، یکی از پرکاربردترین ویژگی‌هایی که برای اشیاء وجود دارد، ویژگی constructor می‌باشد. در واقع این ویژگی نیز یکی از راهکارهای بررسی صحت و اعتبار متغیرها، آرگومانها و اشیا می‌باشد. ویژگی constructor ، به تابع سازنده‌ی یک شیء اشاره می‌کند و آن سازنده را به عنوان خروجی بر میگرداند. دقت داشته باشید که خروجی این ویژگی، خود تابع سازنده می‌باشد و یک مقدار رشته‌ای نیست. به مثال زیر توجه کنید:

var obj = {};
var a = new Array();
var x = 10;

alert(obj.constructor);
alert(obj.constructor === Object);
alert(typeof obj.constructor);
alert(a.constructor);
alert(x.constructor);

خروجی :

    function Object() { [native code] }
    true
    function
    function Array() { [native code] }
    function Number() { [native code] }
همانطور که در مثال فوق مشاهده می‌نمایید، کدهای obj.constructor ، a.constructor و x.constructor تابع سازنده‌ی این اشیا را برگردانده است. در مقایسه obj.constructor===Object نیز مشاهده می‌کنید که خروجی این ویژگی یک شیء می‌باشد و در typeof obj.constructor هم نشان دادیم که نوع این ویژگی یک تابع است.

در اینجا دیگر آماده‌ی ورود به برنامه نویسی شیء گرا در جاوا اسکریپت می‌باشیم که در مقالات بعدی به آن خواهیم پرداخت و همچنین با جزئیات بیشتری اشیاء را تشریح می‌نماییم.