نظرات اشتراک‌ها
معرفی DNTPersianComponents.Blazor
بله. چون هم در طراحی مختلف شکل‌ها و استایل‌های مختلفی وجود دارد که نیازی به دکمه نیست و کلیک روی input به خودی خود کافی است. مثل بسیاری از تقویم‌های جاوااسکریپتی
مطالب
اجرای سرویسهای NodeJS در ASP.NET Core
 این نوشتار در مورد نحوه اجرای سرویس‌های NodeJS در ASP.NET Core می‌باشد؛ زیرا تعداد زیادی از Package‌های سورس باز و با کیفیت بالا به فرم Node package manager یا به اصطلاح NPM موجود و قابل دریافت می‌باشند. NPM بزرگترین مخزن دنیا از لحاظ وجود بسته‌های نرم افزاری سورس باز است. به همین جهت بسته Microsoft.AspNetCore.NodeServices، جهت استفاده از این بسته‌ها در برنامه‌های ASP.NET Core ارائه شده‌است. برای استفاده از سرویسهای Node ابتدا باید ارجاعی را به بسته Microsoft.AspNetCore.NodeServices داشته باشید. برای این منظور میتوانید از دستور dotnet add package Microsoft.AspNetCore.NodeServices استفاده نمایید. البته اگر از آخرین نسخه NET Core. استفاده میکنید، لزومی به ارجاع به بسته فوق نمی‌باشد و به صورت پیشفرض در بسته Microsoft.AspNetCore.All موجود است.
سپس متد ()ConfigurationServices را جهت اضافه کردن میان افزار Node Services به خط لوله درخواستها (request pipeline) ویرایش میکنیم:
public void ConfigureServices(IServiceCollection services)
{
    services.AddMvc();
    services.AddNodeServices();
}

حال میتوانید به وهله‌ای از اینترفیس INodeServices، از طریق تزریق وابستگی‌ها دسترسی داشته باشید. اینترفیس INodeServices یک Api می‌باشد و مشخص می‌کند که کدام قطعه از کد NET. میتواند کد جاوااسکریپتی را که در محیط Node اجرا می‌شود، فراخوانی کند. همچنین میتوانید از خاصیت FromServices برای دریافت وهله‌ای از اینترفیس INodeServices در اکشن خود استفاده نمایید.
public async Task<IActionResult> Add([FromServices] INodeServices nodeServices)
{
    var num1 = 10;
    var num2 = 20;
    var result = await nodeServices.InvokeAsync<int>("AddModule.js", num1, num2);
    ViewData["ResultFromNode"] = $"Result of {num1} + {num2} is {result}";
    return View();
}

سپس کد جاوااسکریپتی متناظر با تابعی را که توسط متد InvokeAsync فراخوانی میشود، به صورت زیر می‌نویسیم:
module.exports = function(callback, num1, num2) { 
  var result = num1 + num2;
  callback(null, result); 
};
دقت کنید که نام فایل جاوااسکریپت باید همنام با پارامتر اول متد InvokeAsync و تعداد پارامترهای تابع باید مساوی با پارامترهایی باشد که به تابع فراخوانی شده جاوااسکریپت ارسال می‌شود.

حال بیاییم مثالی دیگر را مرور کنیم. میخواهیم از صفحه وب درخواستی، عکسی را تهیه کنیم. بدین منظور از کتابخانه url-to-image استفاده میکنیم. برای نصب آن دستور npm install --save url-to-image را در خط فرمان تایپ میکنیم.

بعد از اتمام نصب این بسته، متدی را برای دریافت اطلاعات ارسالی این کتابخانه تدارک میبینیم.

 [HttpPost]
        public async Task<IActionResult> GenerateUrlPreview([FromServices] INodeServices nodeServices)
        {
            var url = Request.Form["Url"].ToString();
            var fileName = System.IO.Path.ChangeExtension(DateTime.UtcNow.Ticks.ToString(), "jpeg");
            var file = await nodeServices.InvokeAsync<string>("UrlPreviewModule.js", url, System.IO.Path.Combine("PreviewImages", fileName));
            return Content($"/Home/Download?img={fileName}");
        }

        public IActionResult Download()
        {
            var image = Request.Query["img"].ToString();
            var fileName = System.IO.Path.Combine("PreviewImages", image);
            var isExists = System.IO.File.Exists(fileName);

            if (isExists)
            {
                Response.Headers.Add($"Content-Disposition", "attachment; filename=\"" + image + "\"");
                var bytes = System.IO.File.ReadAllBytes(fileName);
                return File(bytes, "image/jpeg");
            }
            else
            {
                return NotFound();
            }
        }

سپس متد UrlPreviewModule.js را به صورت زیر مینویسیم:

var urlToImage = require('url-to-image');
module.exports = function (callback, url, imageName) {
    urlToImage(url, imageName).then(function () {
        callback(null, imageName);
    }).catch(function (err) {
        callback(err, imageName);
    });
};


سرویس‌های Node به توسعه دهندگان ASP.NET Core امکان استفاده از اکوسیستم NPM را که دارای قابلیتهای فراوانی می‌باشد، میدهد. 

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

برای تعریف یک سمبل symbol می‌توانید از پیش پردازنده‌ی define# استفاده و برای حذف آن هم از undef# استفاده کنید. رسم هست که سمبل‌ها با حروف بزرگ تعریف شوند.
عبارات #if,#else,#elif,#endif هم عبارات شرطی هستند که می‌توان برای چک کردن یک سمبل از آن‌ها استفاده کرد:
#define DEBUG
...
#if DEBUG
    Console.WriteLine("You have defined DEBUG symbol");
#endif
نتیجه آن را می‌توانید در تصویر زیر مشاهده کنید:

بدیهی است که همین سمبل DEBUG را undef کنید متن بالا نمایش داده نخواهد شد.
بهتر است به پیش پردازنده‌های دیگر هم نگاهی بیندازیم:
#if STANDARD
    Console.WriteLine("You have defined STANDARD symbol");
#elif PROFESSIONAL
    Console.WriteLine("You have defined PROFESSIONAL symbol");
#elif ULTIMATE
    Console.WriteLine("You have defined ULTIMATE symbol");
#endif
حتی می‌توانید از عملگرهای شرطی چون && یا || یا == یا != و... هم استفاده کنید. تکه کد زیر، از این عملگرها بهره جسته است:
#if STANDARD && EVAL
    Console.WriteLine("You have defined STANDARD and EVAL symbols");
#endif

پیش پردازنده‌های #warning و #error
در پیش پردازنده #warning می‌توانید یک پیام هشدار یا اخطار را به پنجره‌ی warning ارسال کنید؛ ولی برنامه کماکان به اجرای خود ادامه می‌دهد. اما با #error برنامه هم پیام خطا را در پنجره مربوطه نمایش می‌دهد و هم باعث halt شدن برنامه می‌شود.
#if STANDARD && EVAL
    Console.WriteLine("You have defined STANDARD and EVAL symbols");
#endif

در کد بالا #warning را با #error جابجا می‌کنیم:


#region و #endregion
از این دو عبارت در بین کدها استفاده می‌کنیم. برای بلوک بندی کد‌ها می‌توان از آن‌ها استفاده کرد. برای مثال دسته بندی کدهای نوشته شده مثل جدا کردن property‌ها یا رویدادها یا متدها و ...، با محصور شدن تکه کدهای بین این دو، یک علامت + یا - برای انجام عمل expand و collapsed ایجاد می‌شود.


#line
برای تغییر نام فایل و شماره خطوط در هنگام دیباگ (نمایش خطا و هشدارها در پنجره‌ی نمایش خطاها) به کار می‌رود.
مثلا به تکه کد زیر دقت کنید و همچنین به تصویر بعد از آن، بدون نوشتن #line  دقت کنید:
namespace CSPreProcessorDirectivesDemo
{
    class Program
    {
        static void Main(string[] args)
        {
            inta a = 100;
            Console.ReadLine();
        }
    }
}

خطای ما در خط 14 فایل program.cs رخ داده است. در تکه کد زیر پیش پردازنده #line را اضافه کردیم:
#line 400 "MyFile.cs"
inta a = 100;

همانطور که می‌بینید آدرس تکه کد یا خط بعد از آن تغییر پیدا کرد و از آنجا به بعد از 400 به بعد شمرده می‌شود.
طبق منابع نوشته شده این پیش پردازنده موقعی بیشتر سودمند هست که تکه کد، توسط ابزارهای خارجی یا سیستمی ویرایش شده باشد.
در صورتیکه از #line default استفاده کنید، از آن نقطه به بعد، نام فایل و شماره خطاها به صورت عادی اعلام می‌شوند و #line قبلی در نظر گرفته نمی‌شود تا شاید اگر دوباره به #line جدیدی برخورد کند.
#line hidden هم تکه کدهای مربوطه را از دید دیباگر مخفی می‌کند مثل موقعیکه برنامه نویس، کد به کد یا خط به خط برنامه را دیباگ می‌کند ولی از اینجا به بعد از روی  این خطوط رد می‌شود تا به یک #line دیگر برسد. منظور از رد شدن، عدم اجرای خطوط نیست؛ بلکه دیباگ خط به خط می‌باشد.

#progma
این پیش پردازنده از دو بخش نام دستور و آگومان‌ها تشکیل شده است:
#pragma pragma-name pragma-arguments
دات نت از دو نام دستور warning و checksum پشتیبانی می‌کند؛ آرگومان‌هایی که با دستور warning می‌پذیرد:
#pragma warning disable
#pragma warning restore
با آرگومان disabled تمامی هشدارهای خطوط بعد از آن نادیده گرفته شده و اعلام نمی‌شوند و از restore برای بازگشت از حالت disabled به کار می‌رود. همچنین برای غیر فعال کردن هشدار برای خط یا خطوط خاص هم میتوانید به صورت زیر بنویسید:
#pragma warning disable 414
#pragma warning disable 414, 3021

#checksum
#pragma checksum "filename" "{guid}" "checksum bytes"
از این یکی برای ذحیره هشدارها و خطاها در program database یا PDB استفاده می‌شود (برای مواقعیکه پروژه شما قرار است به یک com یا dll تبدیل شود؛ کاربردی زیادی دارد). آرگومان اول نام فایل که بعدا برای مانیتور کردن به راحتی بین کلاس‌ها تشخیص داده شود و دومی که GUID است و همین GUID را باید برای فایل مشخص کنید.
// Guid for the interface IMyInterface.
[Guid("F9168C5E-CEB2-4faa-B6BF-329BF39FA1E4")]
interface IMyInterface
{
    void MyMethod();
}

// Guid for the coclass MyTestClass.
[Guid("936DA01F-9ABD-4d9d-80C7-02AF85C822A8")]
public class MyTestClass : IMyInterface
{
    public void MyMethod() {}
}
و checksum _bytes که باید به صورت هگزادسیمال در حالت رشته‌ای نوشته شود و باید بیانگر یک عدد زوج باشد؛ در صورتیکه یک عدد فرد را مشخص کنید، کمپایلر پیش پردازنده شما را در نظر نمی‌گیرد. نهایتا به صورت زیر نوشته می‌شود:
class TestClass
{
    static int Main()
    {
        #pragma checksum "file.cs" "{3673e4ca-6098-4ec1-890f-8fceb2a794a2}" "{012345678AB}" // New checksum
    }
}

منابع :
نظرات مطالب
صفحه بندی و مرتب سازی خودکار اطلاعات به کمک jqGrid در ASP.NET MVC
سلام؛ از مثالی که زدید دارم استفاده میکنم، در کلاس ReflectionHelper  متد FindFieldType موقعی که از جداول خود ارجاع دهنده استفاده میکنیم(در اینجا جدول پست)، خطا stackoverflow می‌گیرم، کجاش رو باید چک کنیم که فراخوانی بی نهایت اتفاق نیفته؟ مگه فیلد dumplevel همچین کاری انجام نمیده؟
data model م به این صورته : 


 
نظرات مطالب
اعمال تزریق وابستگی‌ها به مثال رسمی ASP.NET Identity
Debug.WriteLine در پنجره دیباگ ویژوال استودیو پیام‌ها را نمایش می‌دهد. اگر این پنجره را مشاهده نمی‌کنید این سطر را تبدیل به throw new Exception کنید تا واضح‌تر شود؛ یا روی آن یک break point قرار دهید و پیام‌ها را یکی یکی بررسی کنید.
مطالب
کلاس‌ها در ES 6
رسمی‌ترین زبان‌های شیء گرا از کلاس‌ها و وراثت مربوط به آنها پشتیبانی می‌کنند؛ ولی از زمانی که JavaScript ساخته شد، به دلیل نداشتن کلاس‌ها باعث سردرگمی بیشتر توسعه دهنده‌ها شد. برای آشنایی با مباحث شیء گرایی در جاوااسکریپت  ^ و را مطالعه کنید.
در واقع کلاس‌ها در ES 6 هم واقعا مانند کلاس‌ها در سایر زبان‌ها نبوده و صرفا یک syntax آسان بر فراز روش‌های پیاده سازی انواع داده‌های شخصی در ورژن‌ها قبلی می‌باشند. این syntax به معنای تولید مدل جدید شیء گرایی در JavaScript نمی‌باشد و در ادامه خواهیم دید که این کلاس‌ها چیزی بجز یک function نیستند. در ورژن‌های قبل ES، تعریف نوع داده جدید به عنوان مثال به شکل زیر بود:
function PersonType(name) {
    this.name = name;
}

PersonType.prototype.sayName = function() {
    console.log(this.name);
};

let person = new PersonType("Nicholas");
person.sayName();   // outputs "Nicholas"

console.log(person instanceof PersonType);  // true
console.log(person instanceof Object);      // true
‫در کد بالا که مربوط است به ورژن 5 اکما اسکریپت، PersonType یک تابع سازنده است که دارای یک پراپرتی به نام name و یک متد در سطح آبجکت به نام sayName میباشد.
Class declarations یکی از روش‌های تعریف کلاس در ES 6 میباشد. به عنوان مثال در ورژن جدید، تعریف کلاس مثال فوق به شکل زیر خواهد بود:
class PersonClass {

    // equivalent of the PersonType constructor
    constructor(name) {
        this.name = name;
    }

    // equivalent of PersonType.prototype.sayName
    sayName() {
        console.log(this.name);
    }
}

let person = new PersonClass("Nicholas");
person.sayName();   // outputs "Nicholas"

console.log(person instanceof PersonClass);     // true
console.log(person instanceof Object);          // true

console.log(typeof PersonClass);                    // "function"
console.log(typeof PersonClass.prototype.sayName);  // "function"
در کد بالا این بار به جای تعریف یک  تابع (function) به عنوان سازنده، برای ساخت نوع داده‌ی شخصی، خواهید توانست به صورت مستقیم این سازنده را درون کلاس خود با نام constructor که مشخصا برای این منظور در نظر گرفته شده است، تعریف کنید. همانطور که در خطوط آخر کد بالا مشخص است، کلاس PersonClass چیزی بجز یک function نیست و همین مورد گفته‌های ابتدایی مطلب را تأیید می‌کند.  باید توجه داشت که در تعریف هر کلاسی فقط یک تابع سازنده با نام constructor می‌تواند وجود داشته باشد؛ در غیر این صورت خطای syntax error را دریافت خواهیم کرد.
شباهت‌هایی و معادل‌هایی که در پیاده سازی مثال بالا در دو ورژن مختلف وجود دارد باعث خواهد شد که بدون نگرانی از اینکه با کدام ورژن کار می‌کنید، به صورت ترکیبی از آنها استفاده کنید.
Class Expressions روش دوم پیاده سازی کلاس‌ها در ES 6 می‌باشد؛ به دو صورت named و unnamed که به صورتیکه در زیر مشاهده می‌کنید، قابل تعریف خواهد بود:
//unnamed class expressions do not require identifiers after "class"
let PersonClass = class {

    // equivalent of the PersonType constructor
    constructor(name) {
        this.name = name;
    }

    // equivalent of PersonType.prototype.sayName
    sayName() {
        console.log(this.name);
    }
};

let person = new PersonClass("Nicholas");
person.sayName();   // outputs "Nicholas"

console.log(person instanceof PersonClass);     // true
console.log(person instanceof Object);          // true

console.log(typeof PersonClass);                    // "function"
console.log(typeof PersonClass.prototype.sayName);  // "function"


//named
let PersonClass = class PersonClass2 {

    // equivalent of the PersonType constructor
    constructor(name) {
        this.name = name;
    }

    // equivalent of PersonType.prototype.sayName
    sayName() {
        console.log(this.name);
    }
};

console.log(PersonClass === PersonClass2);  // true
همانطور که متوجه شدید، class‌ها به همانند function‌ها به دو شکل declarations و expressions قابل تعریف هستند (یکی دیگر از شباهت ها). یک نکته در حالت تعریف به صورت named این است که میتوان PerssonClass2 و PerssonClass را به دلیل اینکه هر دوی آنها اشاره‌گر به یک کلاس هستند، به جای هم استفاده کنید.
نکته جالب این که class expressions‌ها را می‌توان به عنوان آرگومان توابع دیگر هم ارسال کرد؛ برای مثال :
function createObject(classDef) {
    return new classDef();
}

let obj = createObject(class {
    sayHi() {
        console.log("Hi!");
    }
});

obj.sayHi();        // "Hi!"
در کد بالا createObject، متدی است که class expression ما به عنوان آرگومان آن پاس داده شده است و در نهایت توانسته‌ایم از این کلاس پاس داده شده در داخل متد نمونه سازی کرده و آن را به عنوان نتیجه‌ی برگشتی return کنیم. 
نکته جالب دیگر این که با استفاده از class expressions‌ها خواهیم توانست singleton‌ها را با فراخوانی بلافاصله‌ی سازنده کلاس، پیاده سازی کنیم. برای این منظور باید کلمه‌ی کلیدی new را قبل از کلمه‌ی کلیدی class نوشته و در پایان هم از دو پرانتز باز و بسته استفاده کنید که معادل فراخوانی سازنده‌ی کلاس خواهد بود.
let person = new class {
    constructor(name) {
        this.name = name;
    }

    sayName() {
        console.log(this.name);
    }
}("Nicholas");

person.sayName();       // "Nicholas  

در کد بالا ، "Nicholas" به عنوان آرگومان سازنده کلاس بی نام در هنگام ساخت نمونه از طریق پرانتز‌های باز و بسته انتهایی، پاس داده شده است. استفاده از class declarations یا class expressions برای کار با کلاس‌ها به سبک کاری شما مربوط خواهد شد و بس. ولی نکته این است که هر دو شکل پیاده سازی کلاس‌ها بر خلاف function declarations و function expressions ، قابلیت  hoisting  را نخواهند داشت و به صورت پیش فرض در حالت strict mode اجرا خواهند شد.

Accessor Properties

کلاس‌ها این امکان را دارند تا بتوان برای پراپرتی‌هایی که در سازنده‌ی کلاس تعریف شده‌اند، accessor property تعریف کرد. سینتکس استفاده شده‌ی برای این منظور، شبیه به ساخت object literal accessor‌ها در ES 5 میباشد.برای مثال:

class CustomHTMLElement {

    constructor(element) {
        this.element = element;
    }

    get html() {
        return this.element.innerHTML;
    }

    set html(value) {
        this.element.innerHTML = value;
    }
}

var descriptor = Object.getOwnPropertyDescriptor(CustomHTMLElement.prototype,\
 "html");

console.log("get" in descriptor);   // true
console.log("set" in descriptor);   // true

در کد بالا ، getter و setter برای محتوای html مربوط به پراپرتی element در نظر گرفته شده است که در واقعا نمایندگان (delegates) مربوط به متد innterHTML خود element می‌باشند. معادل همین پیاده سازی بدون استفاده از سینتکس کلاس، به شکل زیر خواهد بود:

// direct equivalent to previous example
let CustomHTMLElement = (function() {
    "use strict";

    const CustomHTMLElement = function(element) {
        // make sure the function was called with new
        if (typeof new.target === "undefined") {
            throw new Error("Constructor must be called with new.");
        }
        this.element = element;
    }

    Object.defineProperty(CustomHTMLElement.prototype, "html", {
        enumerable: false,
        configurable: true,
        get: function() {
            return this.element.innerHTML;
        },
        set: function(value) {
            this.element.innerHTML = value;
        }
    });
    return CustomHTMLElement;
}());

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

Static Members

ساخت اعضای استاتیک در ورژن قبل برای مثال به شکل زیر بود:

function PersonType(name) {
    this.name = name;
}

// static method
PersonType.create = function(name) {
    return new PersonType(name);
};

// instance method
PersonType.prototype.sayName = function() {
    console.log(this.name);
};

var person = PersonType.create("Nicholas");

 در کد بالا یک متد استاتیک برای نوع داده شخصی PersonType در نظر گرفته شده است. این مورد در ES 6 بهبود یافته و فقط با قرار دادن کلمه‌ی کلیدی static قبل از نام متد و یا accessor property می‌توان به نتیجه‌ی مثال بالا دست یافت:

class PersonClass {

    // equivalent of the PersonType constructor
    constructor(name) {
        this.name = name;
    }

    // equivalent of PersonType.prototype.sayName
    sayName() {
        console.log(this.name);
    }

    // equivalent of PersonType.create
    static create(name) {
        return new PersonClass(name);
    }
}

let person = PersonClass.create("Nicholas");

نکته این که نمی‌توان سازنده‌ی استاتیک در کلاس خود تعریف کرد. 


Inheritance

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

function Rectangle(length, width) {
    this.length = length;
    this.width = width;
}

Rectangle.prototype.getArea = function() {
    return this.length * this.width;
};

function Square(length) {
    Rectangle.call(this, length, length);
}

Square.prototype = Object.create(Rectangle.prototype, {
    constructor: {
        value:Square,
        enumerable: true,
        writable: true,
        configurable: true
    }
});

var square = new Square(3);
console.log(square.getArea());              // 9
console.log(square instanceof Square);      // true
console.log(square instanceof Rectangle);   // true

درکد بالا Square از Rectangle ارث بری کرده که برای این منظور Square.prototype را با ساخت نمونه‌ای از Rectangle.prototype بازنویسی کرده‌ایم. این سینتکس باعث سردرگمی اغلب تازه کاران خواهد شد. برای این منظور در ES 6 خیلی راحت با استفاده از کلمه‌ی کلیدی  extends بعد از نام کلاس و سپس نوشتن نام کلاس پایه خواهیم توانست به نتیجه‌ی بالا دست یابیم. به عنوان مثال:

class Rectangle {

    constructor(length, width) {
        this.length = length;
        this.width = width;
    }

    getArea() {
        return this.length * this.width;
    }
}

class Square extends Rectangle {
    constructor(length) {
        // same as Rectangle.call(this, length, length)
        super(length, length);
    }
}

var square = new Square(3);
console.log(square.getArea());              // 9
console.log(square instanceof Square);      // true
console.log(square instanceof Rectangle);   // true

در کد بالا نیز کلاس Square از کلاس Rectangle ارث بری کرده و همانطور که مشخص است و انتظار داشتیم، متد getArea در یکی از اعضای به ارث برده شده از کلاس پایه، قابل دسترسی می‌باشد. در سازنده‌ی کلاس Square با استفاده از ()super توانسته‌ایم سازنده‌ی کلاس Rectangle را با آرگومان‌های مشخصی فراخوانی کنیم. 

اگر برای subclass، سازنده در نظر گرفته شود، باید سازنده‌ی کلاس پیاده سازی کننده حتما فراخوانی شود. در غیر این صورت با خطا روبرو خواهید شد. ولی در مقابل اگر هیچ سازنده‌ای برای subclass در نظر نگرفته باشید، به صورت خودکار سازنده‌ی کلاس پایه هنگام ساخت نمونه از این subclass فراخوانی خواهد شد:

class Square extends Rectangle {
    // no constructor
}

// Is equivalent to
class Square extends Rectangle {
    constructor(...args) {
        super(...args);
    }
}

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

 چند نکته

- فقط زمانی میتوان ()super را فراخوانی کرد که از بعد از نام کلاس از کلمه‌ی کلیدی extends استفاده شده باشد.
- باید قبل از دسترسی به کلمه‌ی کلیدی this در سازنده subclass، سازنده‌ی کلاس پایه را با استفاده از ()super فراخوانی کرد.
 

Class Methods 

 اگر در subclass متدی همنام متد کلاس پایه داشته باشید، به صورت خودکار متد کلاس پایه override خواهد شد. البته همیشه میتوان متد کلاس پایه را مستقیم هم فراخوانی کرد؛ به عنوان مثال:

class Square extends Rectangle {
    constructor(length) {
        super(length, length);
    }

    // override, shadow, and call Rectangle.prototype.getArea()
    getArea() {
        return super.getArea();
    }
}

در کد بالا متد getArea کلاس پایه بازنویسی شده است. ولی با این حال با استفاده از کلمه‌ی super به متد اصلی در کلاس پایه دسترسی داریم. 

نام متد‌ها حتی می‌توانند قابلیت محاسباتی داشته باشند. به عنوان مثال خواهید توانست به شکل زیر عمل کنید:

let methodName = "getArea";

class Square extends Rectangle {
    constructor(length) {
        super(length, length);
    }

    // override, shadow, and call Rectangle.prototype.getArea()
    [methodName]() {
        return super.getArea();
    }
}

کد بالا دقیقا با مثال قبل یکسان است با این تفاوت که نام متد getArea را به صورت رشته‌ای با قابلیت محاسباتی در نظر گرفتیم.

ارث بردن اعضای استاتیک یک مفهوم جدید در جاوااسکریپت می‌باشد که نمونه‌ی آن را می‌توانید در کد زیر مشاهده کنید:

class Rectangle {
    constructor(length, width) {
        this.length = length;
        this.width = width;
    }

    getArea() {
        return this.length * this.width;
    }

    static create(length, width) {
        return new Rectangle(length, width);
    }
}

class Square extends Rectangle {
    constructor(length) {
        // same as Rectangle.call(this, length, length)
        super(length, length);
    }
}

var rect = Square.create(3, 4);
console.log(rect instanceof Rectangle);     // true
console.log(rect.getArea());                // 12
console.log(rect instanceof Square);        // false

در کد بالا متد استاتیک create یک متد استاتیک در کلاس پایه Rectangle می‌باشد که این بار در کلاس Square هم قابل دسترسی است.

قدرتمندترین جنبه‌ی کلاس‌های مشتق شده در ES 6 ، توانایی ارث بری از expression‌ها می‌باشد. شما می‌توانید کلمه‌ی کلیدی extends را با هر expression ای استفاده کنید. برای مثال:

function Rectangle(length, width) {
    this.length = length;
    this.width = width;
}

Rectangle.prototype.getArea = function() {
    return this.length * this.width;
};

class Square extends Rectangle {
    constructor(length) {
        super(length, length);
    }
}

var x = new Square(3);
console.log(x.getArea());               // 9
console.log(x instanceof Rectangle);    // true

در کد بالا Rectangle یک تابع سازنده برای تعریف نوع داده شخصی در ES 5 و Square، نوع داده با سینتکس کلاس در ES 6 می‌باشند. ولی با این حال کلاس Square توانسته است از Rectangle ارث بری کند.

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

function Rectangle(length, width) {
    this.length = length;
    this.width = width;
}

Rectangle.prototype.getArea = function() {
    return this.length * this.width;
};

function getBase() {
    return Rectangle;
}

class Square extends getBase() {
    constructor(length) {
        super(length, length);
    }
}

var x = new Square(3);
console.log(x.getArea());               // 9
console.log(x instanceof Rectangle);    // true

در کد بالا متد getBase می‌تواند شامل منطق بیشتری هم برای مشخص کردن داینامیک کلاس پایه باشد که این مورد در بعضی از سناریوها مفید خواهد بود.

new.target

با استفاده از مقدار موجود در این شیء، در سازنده‌ی کلاس می‌توان مشخص کرد که به چه شکلی به  کلاس مورد نظر استناد شده‌است. برای مثال:

class Rectangle {
    constructor(length, width) {
        console.log(new.target === Rectangle);
        this.length = length;
        this.width = width;
    }
}

// new.target is Rectangle
var obj = new Rectangle(3, 4);      // outputs true

در کد بالا با استفاده از new.target توانستیم که مشخص کنیم شیء ایجاد شده از نوع Rectangle می‌باشد. با استفاده از این امکان خوب می‌توان به ساخت کلاس‌های abstract رسید. برای مثال:

// abstract base class
class Shape {
    constructor() {
        if (new.target === Shape) {
            throw new Error("This class cannot be instantiated directly.")
        }
    }
}

class Rectangle extends Shape {
    constructor(length, width) {
        super();
        this.length = length;
        this.width = width;
    }
}

var x = new Shape();                // throws error
var y = new Rectangle(3, 4);        // no error
console.log(y instanceof Shape);    // true

در کد بالا که کاملا هم مشخص است؛ در سازنده‌ی کلاس Shape مشخص کرده‌ایم که اگر مستقیما از کلاس Shape نمونه سازی شد، یک exception را پرتاب کند. با این اوصاف ما توانسته‌ایم که کلاس Shape را به صورت Abstract معرفی کنیم.

مطالب
شروع به کار با AngularJS 2.0 و TypeScript - قسمت یازدهم - کار با فرم‌ها - قسمت دوم
در قسمت قبل، فر‌مهای template driven را بررسی کردیم. همانطور که مشاهده کردید، این نوع فرم‌ها، قابلیت‌های اعتبارسنجی پیشرفته‌ای را به همراه ندارند. برای فرم‌هایی که نیاز به اعتبارسنجی‌های سفارشی دارند، فرم‌های model driven پیشنهاد می‌شوند که در این قسمت بررسی خواهند شد.


طراحی فرم ثبت نام کاربران در سایت با روش model driven

در این قسمت قصد داریم فرم ثبت نام کاربران را به همراه اعتبارسنجی‌های پیشرفته‌ای پیاده سازی کنیم. به همین منظور، ابتدا پوشه‌ی جدید App\users را به مثال سری جاری اضافه کنید و سپس سه فایل user.ts، signup-form.component.ts و signup-form.component.html را به آن اضافه نمائید.
فایل user.ts بیانگر مدل کاربران سایت است؛ با این محتوا:
export interface IUser {
    id: number;
    name: string;
    email: string;
    password: string;
}

قالب فرم یا signup-form.component.html، در حالت ابتدایی آن چنین شکل استانداردی را خواهد داشت و فاقد اعتبارسنجی خاصی است:
<form>
    <div class="form-group">
        <label form="name">Username</label>
        <input id="name" type="text" class="form-control" />
    </div>
    <div class="form-group">
        <label form="email">Email</label>
        <input id="email" type="text" class="form-control" />
    </div>
    <div class="form-group">
        <label form="password">Password</label>
        <input id="password" type="password" class="form-control" />
    </div>
    <button class="btn btn-primary" type="submit">Submit</button>
</form>
اکنون می‌خواهیم این فرم را به یک فرم AngularJS 2.0 ارتقاء دهیم. بنابراین نیاز است اشیاء Control و ControlGroup را ایجاد کنیم و اینبار نمی‌خواهیم AngularJS 2.0 مانند قسمت قبلی، به صورت خودکار (و ضمنی)، این اشیاء را برای ما ایجاد کند. می‌خواهیم آن‌ها را با کدنویسی (به صورت صریح) ایجاد کنیم تا بتوانیم بر روی آن‌ها کنترل بیشتری داشته باشیم.
بنابراین ابتدا کلاس کامپوننت این فرم را در فایل signup-form.component.ts به نحو ذیل تکمیل کنید:
import { Component } from '@angular/core';
import { Control, ControlGroup, Validators } from '@angular/common';
 
@Component({
    selector: 'signup-form',
    templateUrl: 'app/users/signup-form.component.html'
})
export class SignupFormComponent {
    form = new ControlGroup({
        name: new Control('', Validators.required),
        email: new Control('', Validators.required),
        password: new Control('', Validators.required)
    });
 
    onSubmit(): void {
        console.log(this.form.value);
    }
}
و همچنین پیام‌های اعتبارسنجی اولیه را نیز به نحو زیر به فایل signup-form.component.html اضافه می‌کنیم:
<form [ngFormModel]="form" (ngSubmit)="onSubmit()">
    <div class="form-group">
        <label form="name">Username</label>
        <input id="name" type="text" class="form-control"
               ngControl="name"/>
        <label class="text-danger" *ngIf="!form.controls['name'].valid">
            Username is required.
        </label>
    </div>
    <div class="form-group">
        <label form="email">Email</label>
        <input id="email" type="text" class="form-control"
               ngControl="email" #email="ngForm"/>
        <label class="text-danger" *ngIf="email.touched && !email.valid">
            Email is required.
        </label>
    </div>
    <div class="form-group">
        <label form="password">Password</label>
        <input id="password" type="password" class="form-control"
               ngControl="password" #password="ngForm"/>
        <label class="text-danger" *ngIf="password.touched && !password.valid">
            Password is required.
        </label>
    </div>
    <button class="btn btn-primary" type="submit">Submit</button>
</form>
توضیحات:
تفاوت مهم این فرم و اعتبارسنجی‌هایش با قسمت قبل، در ایجاد اشیاء Control و ControlGroup به صورت صریح است:
form = new ControlGroup({
    name: new Control('', Validators.required),
    email: new Control('', Validators.required),
    password: new Control('', Validators.required)
});
کلا‌س‌های Control، ControlGroup و Validators در ماژول angular/common@ تعریف شده‌اند. بنابراین import متناظری نیز به ابتدای فایل اضافه شده‌است:
 import { Control, ControlGroup, Validators } from '@angular/common';

یک نکته
اگر محل قرارگیری کلاسی را فراموش کردید، آن‌را در مستندات AngularJS 2.0 ذیل قسمت API Review جستجو کنید. نتیجه‌ی جستجو، به همراه نام ماژول کلاس‌ها نیز می‌باشد.


خاصیت عمومی form که با new ControlGroup تعریف شده‌است، حاوی تعاریف صریح کنترل‌های موجود در فرم خواهد بود. در اینجا سازنده‌ی ControlGroup، یک شیء را می‌پذیرد که کلیدهای آن، همان نام کنترل‌های تعریف شده‌ی در قالب فرم و مقدار هر کدام، یک Control جدید است که پارامتر اول آن یک مقدار پیش فرض و پارامتر دوم، اعتبارسنجی مرتبطی را تعریف می‌کند (این اعتبارسنجی معرفی شده، یک متد استاتیک در کلاس توکار Validators است).
بنابراین چون سه المان ورودی، در فرم جاری تعریف شده‌اند، سه کلید جدید هم نام نیز در پارامتر ورودی ControlGroup ذکر گردیده‌اند.

اکنون که خاصیت عمومی form، در کلاس کامپوننت فوق تعریف شد، آن‌را در قالب فرم، به ngFormModel بایند می‌کنیم:
<form [ngFormModel]="form" (ngSubmit)="onSubmit()">
به این ترتیب به AngularJS 2.0 اعلام می‌کنیم که ControlGroup و Controlهای آن‌را به صورت صریح ایجاد کرده‌ایم و بجای وهله‌‌های پیش فرض خود، از خاصیت عمومی form کلاس کامپوننت، این مقادیر را تامین کن.
مراحل بعد آن، با مراحلی که در قسمت قبل بررسی کردیم، تفاوتی ندارند:
الف) در اینجا به هر المان موجود، یک ngControl نسبت داده شده‌است تا هر المان را تبدیل به یک کنترل AngularJS2 2.0 کند.
ب) به هر المان، یک متغیر محلی شروع شده با # نسبت داده شده‌است تا با اتصال آن به ngForm بتوان به ngControl تعریف شده دسترسی پیدا کرد.
البته اکنون می‌توان از خاصیت form متصل به ngFormModel نیز بجای تعریف این متغیر محلی، به نحو ذیل استفاده کرد:
 <label class="text-danger" *ngIf="!form.controls['name'].valid">
ج) از این متغیر محلی جهت نمایش یا عدم نمایش پیام‌های خطای اعتبارسنجی، در ngIfهای تعریف شده، استفاده شده‌است.
د) و در آخر متد onSumbit موجود در کلاس کامپوننت را به رخداد ngSubmit متصل کرده‌ایم. همانطور که ملاحظه می‌کنید اینبار دیگر پارامتری را به آن ارسال نکرده‌ایم. از این جهت که خاصیت form موجود در سطح کلاس، اطلاعات کاملی را از اشیاء موجود در آن دارد و در متد onSubmit کلاس، به آن دسترسی داریم.
    onSubmit(): void {
        console.log(this.form.value);
    }
this.form.value حاوی یک شیء است که تمام مقادیر پر شده‌ی فرم را به همراه دارد.

بنابراین تا اینجا تنها تفاوت فرم جدید تعریف شده با قسمت قبل، تعریف صریح ControlGroup و کنتر‌ل‌های آن در کلاس کامپوننت و اتصال آن به ngFormModel است. به این نوع فرم‌ها، فرم‌های model driven هم می‌گویند.


نمایش فرم افزودن کاربران توسط سیستم Routing

با نحوه‌ی تعریف مسیریابی‌ها در قسمت نهم آشنا شدیم. برای نمایش فرم افزودن کاربران، می‌توان تغییرات ذیل را به فایل app.component.ts اعمال کرد:
//same as before...
import { SignupFormComponent } from './users/signup-form.component';
 
@Component({
    //same as before…
    template: `
                //same as before…                    
                <li><a [routerLink]="['AddUser']">Add User</a></li>
               //same as before…
    `,
    //same as before…
})
@RouteConfig([
    //same as before…    
    { path: '/adduser', name: 'AddUser', component: SignupFormComponent }
])
//same as before...
ابتدا به RouteConfig، مسیریابی کامپوننت فرم افزودن کاربران، اضافه شده‌است. سپس ماژول این کلاس در ابتدای فایل import شده و در آخر routerLink آن به قالب سایت و منوی بالای سایت اضافه شده‌است.


معرفی کلاس FormBuilder

روش دیگری نیز برای ساخت ControlGroup و کنترل‌های آن با استفاده از کلاس و سرویس فرم ساز توکار AngularJS 2.0 وجود دارد:
import { Control, ControlGroup, Validators, FormBuilder } from '@angular/common';

form: ControlGroup;
 
constructor(formBuilder: FormBuilder) {
    this.form = formBuilder.group({
        name: ['', Validators.required],
        email: ['', Validators.required],
        password: ['', Validators.required]
    });
}
کلاس و سرویس FormBuilder نیز در ماژول angular/common@ قرار دارد. برای استفاده‌ی از آن، آن‌را در سازنده‌ی کلاس تزریق کرده و سپس از متد group آن استفاده می‌کنیم. نحوه‌ی تعریف کلی اعضای آن با اعضای ControlGroup یکی است؛ با این تفاوت که اینبار بجای ذکر new Control، یک آرایه تعریف می‌شود که دقیقا اعضای آن، همان پارامترهای شیء کنترل هستند. این روش در کل خلاصه‌تر است و در آن تعریف چندین گروه مختلف، ساده‌تر می‌باشد. همچنین با روش تزریق وابستگی‌های بکار رفته‌ی در این فریم ورک نیز سازگاری بیشتری دارد.


پیاده سازی اعتبارسنجی سفارشی

فرض کنید می‌خواهیم ورود نام کاربر‌های دارای فاصله را غیر معتبر اعلام کنیم. برای این منظور فایل جدید usernameValidators.ts را به پوشه‌ی app\users اضافه کنید؛ با این محتوا:
import { Control } from '@angular/common';
 
export class UsernameValidators {
    static cannotContainSpace(control: Control) {
        if (control.value.indexOf(' ') >= 0) {
            return { cannotContainSpace: true };
        }
        return null;
    }
}
کلاس UsernameValidators می‌تواند شامل تمام اعتبارسنجی‌های سفارشی خاصیت نام کاربری باشد. به همین جهت نام آن جمع است و به s ختم شد‌ه‌است.
هر متد پیاده سازی کننده‌ی یک اعتبار سنجی سفارشی در این کلاس، استاتیک تعریف می‌شود؛ با نام دلخواهی که مدنظر است.
پارامتر ورودی این متدهای استاتیک، یک وهله از شیء کنترل است که توسط آن می‌توان برای مثال به خاصیت value آن دسترسی یافت و بر این اساس منطق اعتبارسنجی خود را پیاده سازی نمود. به همین جهت import آن نیز به ابتدای فایل جاری اضافه شده‌است.
خروجی این متد دو حالت دارد:
الف) اگر null باشد، یعنی اعتبارسنجی موفقیت آمیز بوده‌است.
ب) اگر اعتبارسنجی با شکست مواجه شود، خروجی این متد یک شیء خواهد بود که کلید آن، نام اعتبارسنجی مدنظر است و مقدار این کلید، هر چیزی می‌تواند باشد؛ یک true و یا یک شیء دیگر که اطلاعات بیشتری را در مورد این شکست ارائه دهد.

برای مثال اگر اعتبارسنج توکار required با شکست مواجه شود، یک چنین شی‌ءایی را بازگشت می‌دهد:
 { required:true }
و یا اگر اعتبارسنج minlength باشکست مواجه شود، اطلاعات بیشتری را در قسمت مقدار این کلید بازگشتی، ارائه می‌دهد:
{
  minlength : {
     requiredLength : 3,
     actualLength : 1
  }
}
در کل اینکه چه چیزی را بازگشت دهید، بستگی به طراحی مدنظر شما دارد.

پس از پیاده سازی یک اعتبارسنجی سفارشی، برای استفاده‌ی از آن، ابتدا ماژول آن‌را به ابتدای ماژول signup-form.component.ts اضافه می‌کنیم:
 import { UsernameValidators } from './usernameValidators';
پس از آن، شبیه به افزودن متد استاتیک توکار Validators.required، این متد جدید را به لیست اعتبارسنجی‌های خاصیت name اضافه می‌کنیم. از آنجائیکه پیشتر المان دوم این آرایه مقدار دهی شده‌است، برای ترکیب چندین اعتبارسنجی با هم، از متد Validators.compose که آرایه‌ای از متدهای اعتبارسنجی را قبول می‌کند، کمک خواهیم گرفت:
 name: ['', Validators.compose([Validators.required, UsernameValidators.cannotContainSpace])],

و مرحله‌ی آخر، نمایش یک پیام اعتبارسنجی مناسب و متناظر با متد cannotContainSpace است. برای این منظور فایل signup-form.component.html را گشوده و تغییرات ذیل را اعمال کنید:
<div class="form-group">
    <label form="name">Username</label>
    <input id="name" type="text" class="form-control"
           ngControl="name"
           #name="ngForm" />
    <div *ngIf="name.touched && name.errors">
        <label class="text-danger" *ngIf="name.errors.required">
            Username is required.
        </label>
        <label class="text-danger" *ngIf="name.errors.cannotContainSpace">
            Username can't contain space.
        </label>
    </div>
</div>
همانطور که در قسمت قبل نیز عنوان شد، چون اکنون به یک المان، بیش از یک اعتبارسنجی اعمال شده‌است، استفاده از خاصیت valid، بیش از اندازه عمومی بوده و باید از خاصیت errors استفاده کرد. به همین جهت این دو اعتبارسنجی را در یک div محصور کننده قرار می‌دهیم و در صورت وجود خطایی، خاصیت name.errors، دیگر نال نبوده و دو برچسب قرار گرفته‌ی در آن بر اساس شرط‌های ngIf آن، پردازش خواهند شد.
نام خاصیت بازگشت داده شده‌ی در اعتبارسنجی سفارشی، به عنوان یک خاصیت جدید شیء errors قابل استفاده است؛ مانند name.errors.cannotContainSpace.

به عنوان تمرین ماژول جدید emailValidators.ts را افزوده و سپس اعتبارسنجی سفارشی بررسی معتبر بودن ایمیل وارد شده را تعریف کنید:
import {Control} from '@angular/common';
 
export class EmailValidators {
    static email(control: Control) {
        var regEx = /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
        var valid = regEx.test(control.value);
        return valid ? null : { email: true };
    }
}
در ادامه آن‌را به لیست formBuilder.group افزوده و همچنین پیام اعتبارسنجی ویژه‌ای را نیز به قالب فرم اضافه کنید (کدهای کامل آن، در فایل zip انتهای بحث موجود است).


یک نکته

اگر نیاز است از regular expressions مانند مثال فوق استفاده شود، می‌توان از متد توکار Validators.pattern نیز استفاده کرد و نیازی به تعریف یک متد جداگانه برای آن وجود ندارد؛ مگر اینکه نیاز به بازگشت شیء خطای کاملتری با خواص بیشتری وجود داشته باشد.


اعتبارسنجی async یا اعتبارسنجی از راه دور (remote validation)

یک سری از اعتبارسنجی‌ها را در سمت کلاینت می‌توان تکمیل کرد؛ مانند بررسی معتبر بودن فرمت ایمیل وارده. اما تعدادی دیگر، نیاز به اطلاعاتی از سمت سرور دارند. برای مثال آیا نام کاربری در حال ثبت، تکراری است یا خیر؟ این نوع اعتبارسنجی‌ها در رده‌ی async validation قرار می‌گیرند.
سازنده‌ی شیء Control در AngularJS 2.0 که در مثال‌های بالا نیز مورد استفاده قرار گرفت، پارامتر اختیاری سومی را نیز دارد که یک AsyncValidatorFn را قبول می‌کند:
 control(value: Object, validator?: ValidatorFn, asyncValidator?: AsyncValidatorFn) : Control
پیاده سازی async validators، بسیار شبیه به سایر اعتبارسنج‌ها هستند. اما از آنجائیکه نیاز به کار با سرور را دارند، استاتیک تعریف کردن آن‌ها، سبب قطع شدن دسترسی آن‌ها از context کلاس جاری شده و امکان تزریق وابستگی‌ها را از دست خواهیم داد. برای مثال دیگر نمی‌توان به سادگی، سرویس دریافت اطلاعات کاربران را در اینجا تزریق کرد. یک راه حل رفع این مشکل، تعریف همان متد اعتبارسنج در کلاس کامپوننت فرم است:
nameShouldBeUnique(control: Control) {
    let name: string = control.value;
    return new Promise((resolve) => {
        this._userService.isUserNameUnique(<IUser>{ "name": name }).subscribe(
            (result: IResult) => {
                resolve(                    
                    result.result ? null : { 'nameShouldBeUnique': true }
                );
            },
            error => {
                resolve(null);
            }
        );
    });
}
و سپس فراخوانی آن به صورت ذیل، به عنوان سومین عنصر آرایه‌ی تعریف شده:
this.form = _formBuilder.group({
    name: ['', Validators.compose([
        Validators.required,
        UsernameValidators.cannotContainSpace
    ]),
        (control: Control) => this.nameShouldBeUnique(control)],
در اینجا با استفاده از arrow functions، امکان دسترسی به این متد تعریف شده‌ی در سطح کلاس، که استاتیک هم نیست، وجود خواهد داشت. به این ترتیب دیگر context کلاس را از دست نداده‌ایم و اینبار می‌توان به this._userService، که آن‌را در ادامه تکمیل خواهیم کرد، بدون مشکلی دسترسی یافت.
امضای متد nameShouldBeUnique تفاوتی با سایر متدهای اعتبارسنج نداشته و پارامتر ورودی آن، همان کنترل است که توسط آن می‌توان به مقدار وارد شده‌ی توسط کاربر دسترسی یافت. اما تفاوت اصلی آن در اینجا است که این متد باید یک شیء Promise را بازگشت دهد. یک Promise، بیانگر نتیجه‌ی یک عملیات async است. در اینجا دو حالت resolve و error را باید پیاده سازی کرد. در حالت error، یعنی عملیات async صورت گرفته با شکست مواجه شده‌است و در حالت resolve، یعنی عملیات تکمیل شده و اکنون می‌خواهیم نتیجه‌ی نهایی را بازگشت دهیم. نتیجه نهایی بازگشت داده شده‌ی در اینجا، همانند سایر validators است و اگر نال باشد، یعنی اعتبارسنجی موفقیت آمیز بوده و اگر یک شیء را بازگشت دهیم، یعنی اعتبارسنجی با شکست مواجه شده‌است.

این Promise، از یک سرویس تعریف شده‌ی در فایل جدید user.service.ts استفاده می‌کند:
import { Injectable } from '@angular/core';
import { Http, Response } from '@angular/http';
import { Observable } from 'rxjs/Observable';
import { Headers, RequestOptions } from '@angular/http';
import { IUser } from  './user';
import { IResult } from './result';
 
@Injectable()
export class UserService {
    private _checkUserUrl = '/home/checkUser';
 
    constructor(private _http: Http) { }
 
    private handleError(error: Response) {
        console.error(error);
        return Observable.throw(error.json().error || 'Server error');
    }
 
    isUserNameUnique(user: IUser): Observable<IResult> {
        let headers = new Headers({ 'Content-Type': 'application/json' }); // for ASP.NET MVC
        let options = new RequestOptions({ headers: headers });
 
        return this._http.post(this._checkUserUrl, JSON.stringify(user), options)
            .map((response: Response) => <IResult>response.json())
            .do(data => console.log("User: " + JSON.stringify(data)))
            .catch(this.handleError);
    }
}
با نحوه‌ی تعریف سرویس‌ها و همچنین کار با سرور و دریافت اطلاعات، در قسمت‌های قبلی بیشتر آشنا شدیم. در اینجا یک درخواست get، به آدرس home/checkuser سرور، ارسال می‌شود. سپس نتیجه‌ی آن در قالب اینترفیس IResult بازگشت داده خواهد شد. این اینترفیس را در فایل result.ts به صورت ذیل تعریف کرده‌ایم:
export interface IResult {
    result: boolean;
}

کدهای سمت سرور برنامه که کار بررسی یکتا بودن نام کاربری را انجام می‌دهند، به صورت ذیل در فایل Controllers\HomeController.cs تعریف شده‌اند:
namespace MVC5Angular2.Controllers
{
    public class HomeController : Controller
    {
        [HttpPost]
        public ActionResult CheckUser(User user)
        {
            var isUnique = new { result = true };
            if (user.Name?.Equals("Vahid", StringComparison.OrdinalIgnoreCase) ?? false)
            {
                isUnique = new { result = false };
            }
 
            return new ContentResult
            {
                Content = JsonConvert.SerializeObject(isUnique, new JsonSerializerSettings
                {
                    ContractResolver = new CamelCasePropertyNamesContractResolver()
                }),
                ContentType = "application/json",
                ContentEncoding = Encoding.UTF8
            };
        }
    }
}
در اینجا اگر نام کاربری وارد شده مساوی Vahid بود، یک شیء anonymous، مطابق امضای اینترفیس IResult سمت کاربر (همان فایل result.ts عنوان شده) بازگشت داده می‌شود.

بنابراین تا اینجا مسیر سمت سرور home/checkuser تکمیل شده‌است. این مسیر توسط سرویس کاربر صدا زده شده و اگر نام کاربری وارد شده موجود باشد، نتیجه‌ای را مطابق امضای قرارداد IResult سفارشی ما بازگشت می‌دهد.
پس از آن مجددا به فایل signup-form.component.ts مراجعه کرده و سرویس جدید UserService را به سازنده‌ی آن تزریق کرده‌ایم. همچنین قسمت providers این کامپوننت را هم جهت تکمیل اطلاعات تزریق کننده‌ی توکار AngularJS 2.0 مقدار دهی کرده‌ایم. البته همانطور که در مبحث تزریق وابستگی‌ها نیز عنوان شد، اگر این سرویس قرار نیست در کلاس دیگری استفاده شود، نیازی نیست تا آن‌را در بالاترین سطح ممکن و در فایل app.component.ts ثبت و معرفی کرد:
@Component({
    selector: 'signup-form',
    templateUrl: 'app/users/signup-form.component.html',
    providers: [ UserService ]
})
export class SignupFormComponent {
 
    constructor(private _formBuilder: FormBuilder, private _userService: UserService) {
پس از ترزیق وابستگی private _userService: UserService، اکنون این سرویس به سادگی و به حالت متداولی در متد nameShouldBeUnique(control: Control) قابل دسترسی خواهد بود و از آن می‌توان جهت اعتبارسنجی‌های غیرهمزمان استفاده کرد.

اکنون که کدهای فعال سازی اعتبارسنجی از راه دور ما تکمیل شده‌است، به فایل signup-form.component.html مراجعه کرده و پیام مناسبی را نمایش خواهیم داد:
<div *ngIf="name.touched && name.errors">
    <label class="text-danger" *ngIf="name.errors.required">
        Username is required.
    </label>
    <label class="text-danger" *ngIf="name.errors.cannotContainSpace">
        Username can't contain space.
    </label>
    <label class="text-danger" *ngIf="name.errors.nameShouldBeUnique">
        This username is already taken.
    </label>
</div>
در ادامه اگر برنامه را اجرا کنید، با ورود نام کاربری Vahid، یک چنین پیام خطایی، مشاهده خواهد شد:



نمایش پیام loading در حین انجام اعتبارسنجی از راه دور

شاید بد نباشد که در حین انجام عملیات اعتبارسنجی از راه دور و ارسال درخواستی به سرور و بازگشت نتیجه‌ی آن، یک پیام loading را نیز نمایش داد. برای انجام این‌کار نیاز است تغییرات ذیل را به فایل signup-form.component.html اضافه کنیم:
<input id="name" type="text" class="form-control"
       ngControl="name"
       #name="ngForm" />
<div *ngIf="name.control.pending">
    Checking server, Please wait ...
</div>
در اینجا یک div جدید را ذیل المان ورود نام کاربری اضافه کرده‌ایم. همچنین نحوه‌ی نمایش آن‌را با دسترسی به متغیر name# و کنترل منتسب، به آن مدیریت می‌کنیم. اگر عملیات async ایی بر روی این کنترل در حال اجرا باشد، Promise تعریف شده، وضعیت pending را بازگشت می‌دهد. به همین جهت می‌توان از این خاصیت، جهت نمایش دادن یا مخفی کردن عبارت و یا تصویری استفاده کرد.

 
اعتبارسنجی ترکیبی در حین submit یک فرم

فرض کنید می‌خواهید منطقی را که حاصل اعتبارسنجی تمام فیلدهای فرم است (و نه هر کدام به تنهایی)، در حین submit آن اعمال کنید. برای مثال آیا ترکیب نام کاربری و کلمه‌ی عبور شخصی در حین login معتبر است یا خیر؟ در این حالت پس از بررسی‌های لازم در متد onSubmit، می‌توان با استفاده از متد find شیء form، به یکی از کنترل‌های فرم دسترسی یافت و سپس با استفاده از متد setErrors، خطای اعتبارسنجی سفارشی را به آن اضافه کرد:
onSubmit(): void {
    console.log(this.form.value);
 
    this.form.find('name').setErrors({
        invalidData : true
    }); 
}
سپس در سمت قالب این کامپوننت، نحوه‌ی نمایش این اعتبارسنجی سفارشی، همانند قبل است:
<div *ngIf="name.touched && name.errors">
    <label class="text-danger" *ngIf="name.errors.invalidData">
        Check the inputs....
    </label>
</div>


اتصال المان‌های فرم به مدلی جهت ارسال به سرور

اکنون که دسترسی به خاصیت this.form را داریم و این خاصیت توسط [ngFormModel] به تمام اشیاء تعریف شده‌ی در فرم و تغییرات آن‌ها دسترسی دارد، می‌توان از آن برای دسترسی به شیء‌ایی که حاوی مدل فرم است، استفاده کرد. برای نمونه در مثال فوق، خاصیت value آن، چنین خروجی را دارد:
  { name="VahidN", email="email@site.com", password="123"}
بنابراین برای ارسال اطلاعات این فرم به سرور، تنها کافی است این شیء را ارسال کنیم. به همین جهت در فایل user.service.ts، به کلاس سرویس کاربران، متد addUser را اضافه می‌کنیم:
import { Injectable } from '@angular/core';
import { Http, Response } from '@angular/http';
import { Observable } from 'rxjs/Observable';
import { Headers, RequestOptions } from '@angular/http';
import { IUser } from  './user';
import { IResult } from './result';
 
@Injectable()
export class UserService {
    private _addUserUrl = '/home/addUser';
 
    constructor(private _http: Http) { }
 
    private handleError(error: Response) {
        console.error(error);
        return Observable.throw(error.json().error || 'Server error');
    }
 
    addUser(user: IUser): Observable<IUser> {
        let headers = new Headers({ 'Content-Type': 'application/json' }); // for ASP.NET MVC
        let options = new RequestOptions({ headers: headers });
 
        return this._http.post(this._addUserUrl, JSON.stringify(user), options)
            .map((response: Response) => <IUser>response.json())
            .do(data => console.log("User: " + JSON.stringify(data)))
            .catch(this.handleError);
    }
}
کدهای سمت سرور آن در فایل Controllers\HomeController.cs نیز چنین شکلی را می‌توانند داشته باشند:
[HttpPost]
public ActionResult AddUser(User user)
{
    user.Id = 1; //todo: save user and get id from db
 
    return new ContentResult
    {
        Content = JsonConvert.SerializeObject(user, new JsonSerializerSettings
        {
            ContractResolver = new CamelCasePropertyNamesContractResolver()
        }),
        ContentType = "application/json",
        ContentEncoding = Encoding.UTF8
    };
}
و پس از آن کدهای متد onSubmit فایل signup-form.component.ts برای ارسال این شیء به صورت ذیل خواهند بود:
onSubmit(): void {
    console.log(this.form.value);
 
    /*this.form.find('name').setErrors({
            invalidData : true
        });*/
 
    this._userService.addUser(<IUser>this.form.value)
        .subscribe((user: IUser) => {
            console.log(`ID: ${user.id}`);
        });
}


کدهای کامل این قسمت را از اینجا می‌توانید دریافت کنید: (این کدها مطابق نگارش RC 1 هستند)
MVC5Angular2.part11.zip


خلاصه‌ی بحث

برای اینکه بتوان کنترل بیشتری را بر روی المان‌های فرم داشت، ابتدا سرویس FormBuilder را در سازنده‌ی کلاس کامپوننت فرم تزریق می‌کنیم. سپس با استفاده از متد group آن، المان‌های فرم را به صورت کلیدهای شیء پارامتر آن تعریف می‌کنیم. در اینجا می‌توان اعتبارسنجی‌های توکار AngularJS 2.0 را که در کلاس پایه‌ی Validators مانند Validators.required وجود دارند، تعریف کرد. با استفاده از متد compose آن‌ها را ترکیب نمود و یا پارامتر سومی را جهت اعتبارسنجی‌های async اضافه نمود. در این حالت شیء form تعریف شده به صورت [ngFormModel] به قالب فرم متصل می‌شود و از تغییرات آن آگاه خواهد شد.
مطالب
معرفی Kendo UI
Kendo UI چیست؟
Kendo UI یک فریم ورک جاوا اسکریپتی ساخت برنامه‌های مدرن و تعاملی وب است و برای رسیدن به این مقصود، از JavaScript، CSS 3، HTML 5 و jQuery کمک می‌گیرد.


امکانات فراهم شده توسط Kendo UI
1) انواع و اقسام ویجت‌ها: کنترل‌های وب تهیه شده برفراز jQuery
ویجت‌های آن در سه گروه کلی قرار می‌گیرند:
- گروه وب، مانند گرید، tree-view و غیره.
- گروه DataViz که جهت نمایش بصری اطلاعات و ترسیم انواع و اقسام نمودارها کاربرد دارد.
- گروه موبایل که با استفاده از فناوری adaptive rendering، در سیستم عامل‌های مختلف موبایل، مانند اندروید و آی او اس، ظاهری بومی و هماهنگ با آن‌ها را ارائه می‌دهد.

2) منبع داده سمت کاربر (Client side data source)
منبع داده سمت کاربر Kendo UI، از انواع و اقسام منابع داده محلی مانند آرایه‌های جاوا اسکریپتی تا منابع داده راه دور، مانند JSON، XML و JSONP، جهت نمایش اطلاعات و data binding پشتیبانی می‌کند. این منبع داده، مواردی مانند صفحه بندی، مرتب سازی اطلاعات و گروه بندی آن‌ها را نیز فراهم می‌کند. به علاوه با عملیات ثبت، ویرایش و حذف اطلاعات نیز هماهنگی کاملی را دارد.

3) به همراه یک فریم ورک MVVM توکار است
این فریم ورک MVVM مواردی مانند two way data binding و همچنین declarative binding را نیز پشتیبانی می‌کند.

4) امکان تعویض قالب
5) پویا نمایی، کشیدن و رها کردن
6) فریم ورک اعتبارسنجی


چرا Kendo UI؟
-  مهم‌ترین مزیت کار با Kendo UI، فراهم آوردن تمام نیازهای توسعه‌ی یک برنامه‌ی مدرن وب، تنها در یک بسته است. به این ترتیب دیگر نیازی نیست تا گرید را از یک‌جا، tree-view را از جایی دیگر و کتابخانه‌های رسم نمودار را از منبعی ناهمگون با سایر عناصر برنامه دریافت و استفاده کنید؛ در اینجا تمام این‌ها در قالب یک بسته‌ی آماده برای شما فراهم شده‌است و همچنین با یکدیگر سازگاری کاملی دارند.
- تمام ویجت‌های آن برای نمایش سریع با کارآیی بالا طراحی شده‌اند.
- پشتیبانی خوب آن. این فریم ورک محصول شرکتی است که به صورت تخصصی کار تهیه کامپوننت‌های وب و دسکتاپ را انجام می‌دهد.


مرورگرهای پشتیبانی شده
یکی دیگر از مزایای مهم کار با Kendo UI پشتیبانی گسترده‌ی آن از اکثر مرورگرهای موجود است. این فریم ورک با مرورگرهای زیر سازگار است:
- IE 7 به بعد
- فایرفاکس 10 به بعد
- تمام نگارش‌های کروم
- اپرا 10 به بعد
- سفاری 4 به بعد


مجوز استفاده از Kendo UI
Kendo UI با سه مجوز ذیل ارائه می‌شود:
- 30 روزه آزمایشی رایگان
- تجاری
- سورس باز با مجوز Apache

پیشتر نسخه‌ی تجاری آن تحت مجوز GPL نیز در دسترس بود. اما اخیرا مجوز GPL آن حذف شده و به Apache تغییر یافته است. اما باید در نظر داشت که نسخه‌ی سورس باز آن شامل کنترل‌های مهمی مانند «گرید» نیست و این موارد تنها در نسخه‌ی تجاری آن لحاظ شده‌اند.


مثال‌های Kendo UI
پس از دریافت بسته‌ی کامل آن، پوشه‌هایی مانند js، styles و امثال آن قابل مشاهده هستند؛ به همراه پوشه‌ی examples آن که حداقل 86 پوشه‌ی دیگر در آن جهت ارائه مثال‌هایی از نحوه‌ی کاربرد المان‌های مختلف آن تدارک دیده شده‌اند.


نحوه‌ی افزودن Kendo UI به صفحه
از آنجائیکه Kendo UI یک فریم ورک جاوا اسکریپتی است، همانند سایر برنامه‌های وب، افزودن تعاریف فایل‌های js، css و تصاویر مرتبط با آن، برای شروع به کار کفایت می‌کند. برای این منظور ابتدا پوشه‌های js و styles بسته‌ی دریافتی آن‌را به برنامه‌ی خود اضافه کنید (این پوشه‌ها در فایل پیوست انتهای بحث موجود هستند).
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
    <title></title>

    <!--KendoUI: Web-->
    <link href="styles/kendo.common.min.css" rel="stylesheet" type="text/css" />
    <link href="styles/kendo.default.min.css" rel="stylesheet" type="text/css" />
    <script src="js/jquery.min.js" type="text/javascript"></script>
    <script src="js/kendo.web.min.js" type="text/javascript"></script>

    <!--KendoUI: DataViz-->
    <link href="styles/kendo.dataviz.min.css" rel="stylesheet" type="text/css" />
    <script src="js/kendo.dataviz.min.js" type="text/javascript"></script>

    <!--KendoUI: Mobile-->
    <link href="styles/kendo.mobile.all.min.css" rel="stylesheet" type="text/css" />
    <script src="js/kendo.mobile.min.js" type="text/javascript"></script>

    <script type="text/javascript">
        $(function() {
            $("#pickDate").kendoDatePicker();
        });
    </script>
</head>
<body>
    <span>
        Pick a date: <input id="pickDate" type="text"/>
    </span>
</body>
</html>
در اینجا یک مثال ساده‌ی استفاده از date picker کندو یو آی را ملاحظه می‌کنید. در قسمت head صفحه، نحوه‌ی ثبت سه گروه اسکریپت و شیوه نامه، مشخص شده‌اند. اگر نیاز به کامپوننت‌های وب آن‌را دارید باید اجزایی مانند kendo.common.min.css، kendo.default.min.css، jquery.min.js و kendo.web.min.js به صفحه اضافه شوند. اگر نیاز به رسم نمودار هست، فایل‌ها kendo.dataviz.min.css و kendo.dataviz.min.js باید تعریف شوند و برای فعال سازی اجزای موبایل آن فایل‌های kendo.mobile.all.min.css و kendo.mobile.min.js نیاز است به صفحه پیوست شوند. در هر سه حالت ذکر jquery.min.js الزامی است.

دریافت سورس کامل این قسمت که حاوی فایل‌های اصلی kendoui.professional.2014.2.1008 نیز می‌باشد:
KendoUI01.7z