روشهای مختلف تعریف متغیرها در TypeScript
تمام توسعه دهندههای JavaScript با واژهی کلیدی var آشنایی دارند؛ اما TypeScript واژههای کلیدی let و const را نیز اضافه کردهاست (که جزئی از ES 6 نیز میباشند). تفاوت مهم بین var و let، در میدان دید متغیرهای تعریف شدهی توسط آنها خلاصه میشود. پیشتر در سری مباحث بررسی ES 6، مطلب «متغیرها در ES 6» را نیز بررسی کردیم که در TypeScript نیز صادق میباشند؛ با این تفاوت که TypeScript را میتوان به ES 5 نیز کامپایل کرد و بدون مشکل با تمام مرورگرهای موجود، اجرا نمود.
- متغیرهایی که با var تعریف میشوند، به صورت سراسری در متدی که تعریف شدهاند، قابل دسترسی هستند؛ حتی اگر 5 سطح داخل ifهای تو در تو تعریف شده باشند. اما let و const تنها در block و قطعهای که معرفی شدهاند، معتبر بوده و خارج از آن تعریف نشدهاند. در اینجا یک block توسط {} معرفی میشود.
- متغیرهای از نوع var به دلیل مفهومی به نام hoisting توسط runtime جاوا اسکریپت، به بالاترین سطح متد منتقل میشوند. به همین دلیل عمق تعریف آنها، اثری در دسترسی به این متغیرها ندارد. اما hoisting در مورد let و const اعمال نمیشود.
- متغیرهای var را میتوان چندبار مجددا تعریف کرد (هرچند این روش توصیه نمیشود؛ اما مجاز است). یک چنین تعریف مجددی با متغیرهای از نوع let و const مجاز نیست.
برای توضیحات بیشتر به مثال ذیل دقت کنید:
در اینجا داخل قطعهی if تعریف شده، یک متغیر، از نوع var و متغیر دیگری از نوع let تعریف شدهاست. خارج از بدنهی این متد، متغیر foo هنوز قابل دسترسی است، اما متغیر bar خیر.
نوعهای پایهی TypeScript
نوعهای پایهی TypeScript شامل موارد ذیل هستند:
Boolean: برای ذخیره سازی true یا false.
Number: معرف مقادیر عددی اعشاری هستند.
String: مقادیر رشتهای را ذخیره میکند.
Array: روشهای متفاوتی برای تعریف آرایهها در TypeScript وجود دارند که در ادامه آنها را بیشتر بررسی خواهیم کرد.
Enum: نوعهای شمارشی؛ جهت دادن نامهایی بهتر به مجموعهی مشخصی از مقادیر.
Any: به این معنا که یک متغیر میتواند به هر نوع دلخواهی اشاره کند. هدف از وجود این نوع، امکان استفادهی از کتابخانههای فعلی جاوا اسکریپتی است که نوعی برای متغیرهای آنها تعریف نشدهاند و در اساس هر نوعی میتوانند باشند. برای مثال در جاوا اسکریپت مجاز است در سطر اول متغیری را تعریف کرد و در سطر دوم به آن یک رشته و در سطر سوم به آن یک عدد را انتساب داد.
Void: به معنای فقدان یک نوع است. برای مثال مشخص کردن اینکه متدی، خروجی ندارد.
یک نکته: قابلیت Template string در ES 6 نیز در TypeScript پشتیبانی میشود.
مفهوم Type Inference در TypeScript
در TypeScript الزاما نیازی نیست تا نوع متغیری را صریحا مشخص کرد. در اینجا اگر نوع متغیری را در ابتدای کار تعریف نکنید، نوع آن در اولین باری که مقدار دهی میشود، مشخص خواهد شد که به آن Type Inference نیز میگویند.
در مثال فوق، نوع متغیر myString در زمان تعریف آن مشخص نشدهاست؛ اما با یک رشته، مقدار دهی و آغاز شدهاست. بر این اساس، TypeScript نوع این متغیر را رشتهای در نظر میگیرد و در سطر بعدی، از انتساب یک مقدار عددی به آن جلوگیری خواهد کرد.
و یا در مثال ذیل، نوع خروجی متد ReturnNumber به صورت صریح مشخص نشدهاست:
اما با توجه به اینکه خروجی آن عددی است، نوع آن نیز عددی درنظر گرفته شده و از انتساب آن به یک متغیر رشتهای جلوگیری میشود.
تعریف صریح نوعها در TypeScript با استفاده از Type Annotations
برای لغو Type Inference و تعیین صریح نوعها در TypeScript میتوان به صورت زیر عمل کرد:
با قراردادن یک کولن پس از نام متغیر و سپس تعریف نوع دادهی مدنظر، میتوان نوعها را در TypeScript به صورت صریحی تعریف کرد. در مورد متدها نیز به همین صورت است. کولن پس از پایان بسته شدن پرانترها قرار میگیرد و سپس نوع بازگشتی متد مشخص میگردد.
نوعهای شمارشی در TypeScript
Enums در بسیاری از زبانهای برنامه نویسی متداول وجود دارند و هدف از آنها، دادن نامهایی بهتر و مشخص، به مجموعهای از مقادیر است:
در مثال فوق یک enum جهت تعریف گروههای کتابها تعریف شدهاست. در اینجا بجای استفاده از مقادیر نامفهوم 0 تا 2، نامهایی مشخص و بهتر، جهت معرفی آنها ارائه شدهاند. به این ترتیب میتوان در نهایت به کدهایی خواناتر رسید.
اگر میخواهید این مقادیر با اعداد دیگری شروع شوند (بجای صفر پیش فرض)، میتوان مقدار اولین نام را به صورت صریحی مشخص کرد:
و یا الزامی به استفاده از مقادیر افزایش یابندهای در اینجا نیست. در صورت نیاز میتوان مقدار هر المان را جداگانه تعریف کرد:
پس از اینکه نوع enum تعریف شده، استفاده از آن همانند سایر نوعهای پایهی TypeScript است:
در اینجا متغیر favoriteCategory از نوع enum گروه کتابها تعیین شدهاست؛ با مقدار اولیهی Category.Biography.
در این حالت اگر مقدار favoriteCategory را چاپ کنیم، خروجی عددی 5 نمایش داده میشود:
اگر نیاز به بازگشت مقدار رشتهای این آیتم است، میتوان از ایندکسهای یک enum استفاده کرد:
آرایهها در TypeScript
در حالت عمومی، آرایهها در TypeScript همانند جاوا اسکریپت تعریف میشوند؛ البته به همراه تعدادی استثناء. در TypeScript سه روش برای تعریف آرایهها وجود دارند:
الف) در مثال زیر آرایهای از رشتهها تعریف شدهاست. در اینجا نوع آرایه به همراه [] مشخص میشود:
ب) روش دوم تعریف آرایهها در TypeScript با استفاده از مفهوم Generics است که در بحثی جداگانه به آن خواهیم پرداخت. در اینجا از واژهی کلیدی Array به همراه مشخص سازی نوع آن در داخل <> استفاده شدهاست:
ج) اگر نیاز به آرایههایی شبیه به جاوا اسکریپت دارید که میتوانند حاوی انواع و اقسام المانهایی با نوعهای مختلف باشند، نوع آرایه را []any تعریف کنید:
نوع Tuples در TypeScript
Tuples در TypeScript نوع خاصی از آرایهها هستند که نوع مقادیر اعضای آنها به صورت صریح مشخص میشوند:
برای مثال در اینجا نوع متغیر myTuple به صورت آرایهای از دو عنصر عددی و رشتهای تعریف شدهاست.
اکنون برای دسترسی به مقادیر این المانها، همانند کار با آرایههای معمولی، از ایندکسهای آرایهی تعریف شده استفاده میشود:
یک نکته: میتوان به آرایهی تعریف شده، عناصر جدیدی را نیز افزود؛ با این شرط که نوع آنها، یکی از نوعهای مشخص شدهی در تعریف Tuple باشند:
مفهوم Type assertions در TypeScript
حتما با مفهوم cast و تبدیل نوعهای مختلف به یکدیگر، در زبانهای دیگر برنامه نویسی آشنا هستید. در TypeScript نیز این مفهوم تحت عنوان Type assertions پشتیبانی میشود و دو روش برای تعریف آن وجود دارد:
الف) تعریف cast توسط angle-bracket syntax که در آن نوع مدنظر داخل یک <> قرار میگیرد:
در اینجا نوع نامشخص any به string تبدیل شده و سپس امکان دسترسی به خاصیت طول آن که تحت کنترل کامپایلر است و قابل انتساب به یک مقدار عددی، میسر شدهاست.
ب) تعریف cast توسط as syntax به نحو ذیل:
هر دو حالت تعریف شده معادل هستند. فقط باید دقت داشت در حین کار با ReactJS توسط TypeScript، فقط حالت as syntax پشتیبانی میشود.
تمام توسعه دهندههای JavaScript با واژهی کلیدی var آشنایی دارند؛ اما TypeScript واژههای کلیدی let و const را نیز اضافه کردهاست (که جزئی از ES 6 نیز میباشند). تفاوت مهم بین var و let، در میدان دید متغیرهای تعریف شدهی توسط آنها خلاصه میشود. پیشتر در سری مباحث بررسی ES 6، مطلب «متغیرها در ES 6» را نیز بررسی کردیم که در TypeScript نیز صادق میباشند؛ با این تفاوت که TypeScript را میتوان به ES 5 نیز کامپایل کرد و بدون مشکل با تمام مرورگرهای موجود، اجرا نمود.
- متغیرهایی که با var تعریف میشوند، به صورت سراسری در متدی که تعریف شدهاند، قابل دسترسی هستند؛ حتی اگر 5 سطح داخل ifهای تو در تو تعریف شده باشند. اما let و const تنها در block و قطعهای که معرفی شدهاند، معتبر بوده و خارج از آن تعریف نشدهاند. در اینجا یک block توسط {} معرفی میشود.
- متغیرهای از نوع var به دلیل مفهومی به نام hoisting توسط runtime جاوا اسکریپت، به بالاترین سطح متد منتقل میشوند. به همین دلیل عمق تعریف آنها، اثری در دسترسی به این متغیرها ندارد. اما hoisting در مورد let و const اعمال نمیشود.
- متغیرهای var را میتوان چندبار مجددا تعریف کرد (هرچند این روش توصیه نمیشود؛ اما مجاز است). یک چنین تعریف مجددی با متغیرهای از نوع let و const مجاز نیست.
برای توضیحات بیشتر به مثال ذیل دقت کنید:
function ScopeTest() { if (true) { var foo = 'use anywhere'; let bar = 'use in this block'; } console.log(foo); // works! console.log(bar); // error! }
نوعهای پایهی TypeScript
نوعهای پایهی TypeScript شامل موارد ذیل هستند:
Boolean: برای ذخیره سازی true یا false.
let isDone: boolean = false;
let decimal: number = 6; let hex: number = 0xf00d; let binary: number = 0b1010; let octal: number = 0o744;
let name: string = "bob"; name = 'smith';
let list: number[] = [1, 2, 3];
enum Color {Red, Green, Blue}; let c: Color = Color.Green;
let notSure: any = 4; notSure = "maybe a string instead"; notSure = false; // okay, definitely a boolean
function warnUser(): void { alert("This is my warning message"); }
یک نکته: قابلیت Template string در ES 6 نیز در TypeScript پشتیبانی میشود.
مفهوم Type Inference در TypeScript
در TypeScript الزاما نیازی نیست تا نوع متغیری را صریحا مشخص کرد. در اینجا اگر نوع متغیری را در ابتدای کار تعریف نکنید، نوع آن در اولین باری که مقدار دهی میشود، مشخص خواهد شد که به آن Type Inference نیز میگویند.
let myString= 'this is a string'; myString= 42; // error!
و یا در مثال ذیل، نوع خروجی متد ReturnNumber به صورت صریح مشخص نشدهاست:
function ReturnNumber() { return 42; } let anotherString = 'this is also a string'; anotherString = ReturnNumber(); // error!
تعریف صریح نوعها در TypeScript با استفاده از Type Annotations
برای لغو Type Inference و تعیین صریح نوعها در TypeScript میتوان به صورت زیر عمل کرد:
let myString : string = 'this is a string'; myString = 42; // error! function ReturnNumber() : number { return 42; } let anotherString : string = 'this is also a string'; anotherString = ReturnNumber(); // error!
نوعهای شمارشی در TypeScript
Enums در بسیاری از زبانهای برنامه نویسی متداول وجود دارند و هدف از آنها، دادن نامهایی بهتر و مشخص، به مجموعهای از مقادیر است:
enum Category { Biography, Poetry, Fiction }; // 0, 1, 2
اگر میخواهید این مقادیر با اعداد دیگری شروع شوند (بجای صفر پیش فرض)، میتوان مقدار اولین نام را به صورت صریحی مشخص کرد:
enum Category { Biography = 1, Poetry, Fiction }; // 1, 2, 3
enum Category{ Biography = 5, Poetry = 8, Fiction = 9 }; // 5, 8, 9
let favoriteCategory: Category = Category.Biography;
در این حالت اگر مقدار favoriteCategory را چاپ کنیم، خروجی عددی 5 نمایش داده میشود:
console.log(favoriteCategory); // 5
let categoryString= Category[favoriteCategory]; // Biography
آرایهها در TypeScript
در حالت عمومی، آرایهها در TypeScript همانند جاوا اسکریپت تعریف میشوند؛ البته به همراه تعدادی استثناء. در TypeScript سه روش برای تعریف آرایهها وجود دارند:
الف) در مثال زیر آرایهای از رشتهها تعریف شدهاست. در اینجا نوع آرایه به همراه [] مشخص میشود:
let strArray1: string[] = ['here', 'are', 'strings'];
let strArray2: Array<string> = ['more', 'strings', 'here'];
let anyArray: any[] = [42, true, 'banana'];
نوع Tuples در TypeScript
Tuples در TypeScript نوع خاصی از آرایهها هستند که نوع مقادیر اعضای آنها به صورت صریح مشخص میشوند:
let myTuple: [number, string] = [25, 'truck'];
اکنون برای دسترسی به مقادیر این المانها، همانند کار با آرایههای معمولی، از ایندکسهای آرایهی تعریف شده استفاده میشود:
let firstElement= myTuple[0]; // 25 let secondElement= myTuple[1]; // truck
یک نکته: میتوان به آرایهی تعریف شده، عناصر جدیدی را نیز افزود؛ با این شرط که نوع آنها، یکی از نوعهای مشخص شدهی در تعریف Tuple باشند:
// other elements can have numbers or strings myTuple[2] = 100; myTuple[2] = 'this works!';
مفهوم Type assertions در TypeScript
حتما با مفهوم cast و تبدیل نوعهای مختلف به یکدیگر، در زبانهای دیگر برنامه نویسی آشنا هستید. در TypeScript نیز این مفهوم تحت عنوان Type assertions پشتیبانی میشود و دو روش برای تعریف آن وجود دارد:
الف) تعریف cast توسط angle-bracket syntax که در آن نوع مدنظر داخل یک <> قرار میگیرد:
let someValue: any = "this is a string"; let strLength: number = (<string>someValue).length;
ب) تعریف cast توسط as syntax به نحو ذیل:
let someValue: any = "this is a string"; let strLength: number = (someValue as string).length;
مطالب
ASP.NET MVC #6
آشنایی با انواع ActionResult
در قسمت چهارم، اولین متد یا اکشنی که به صورت خودکار توسط VS.NET به برنامه اضافه شد، اینچنین بود:
using System.Web.Mvc;
namespace MvcApplication1.Controllers
{
public class HomeController : Controller
{
//
// GET: /Home/
public ActionResult Index()
{
return View();
}
}
}
توضیحات تکمیلی مرتبط با خروجی از نوع ActionResult ایی را که مشاهده میکنید، در این قسمت ارائه خواهد شد.
رفتار یک کنترلر توسط متدهایی که در آن کلاس تعریف میشوند، مشخص میگردد. هر متد هم از طریق یک URL مجزا قابل دسترسی و فراخوانی خواهد بود. این متدها که به آنها اکشن نیز گفته میشود باید عمومی بوده، استاتیک یا متد الحاقی (extension method) نباشند و همچنین دارای پارامترهایی از نوع ref و out نیز نباشند.
هر درخواست رسیده، به یک کنترلر و متدی عمومی در آن توسط سیستم مسیریابی، نگاشت خواهد شد. اگر علاقمند باشید که در یک کلاس کنترلر، متدی عمومی را از این سیستم خارج کنید، تنها کافی است آنرا با ویژگی (attribute) به نام NonAction مزین کنید:
using System.Web.Mvc;
namespace MvcApplication2.Controllers
{
public class HomeController : Controller
{
[NonAction]
public string ShowData()
{
return "Text";
}
public ActionResult Index()
{
ViewBag.Message = string.Format("{0}/{1}/{2}",
RouteData.Values["controller"],
RouteData.Values["action"],
RouteData.Values["id"]);
return View();
}
public ActionResult Search(string data = "*")
{
// do something ...
return View();
}
}
}
چند نکته در این مثال قابل ذکر است:
الف) در اینجا اگر شخصی آدرس http://localhost/home/showdata را درخواست نماید، با توجه به استفاده از ویژگی NonAction، با پیغام یافت نشد یا 404 مواجه میگردد.
ب) صرفنظر از پارامترهای یک متد و ساختار کلاس جاری، اطلاعات مسیریابی از طریق شیء RouteData.Values نیز در دسترس هستند که نمونهای از آنرا در اینجا بر اساس مقادیر پیش فرض تعاریف مسیریابی یک پروژه ASP.NET MVC ملاحظه مینمائید.
ج) در متد Search، از قابلیت امکان تعریف مقداری پیش فرض جهت آرگومانها در سی شارپ 4 استفاده شده است. به این ترتیب اگر شخصی آدرس http://localhost/home/search را وارد کند، چون پارامتری را ذکر نکرده است، به صورت خودکار از مقدار پیش فرض آرگومان data استفاده میگردد.
انواع Action Results در ASP.NET MVC
در ASP.NET MVC بجای استفاده مستقیم از شیء Response، از شیء ActionResult جهت ارائه خروجی یک متد استفاده میشود و مهمترین دلیل آن هم مشکل بودن نوشتن آزمونهای واحد برای شیء Response است که وهله سازی آن مساوی است با به کار اندازی موتور ASP.NET و Http Runtime آن توسط یک وب سرور (بنابراین در ASP.NET MVC سعی کنید شیء Response را فراموش کنید).
سلسه مراتب ActionResultهای قابل استفاده در ASP.NET در تصویر زیر مشخص شدهاند:
و در مثال زیر تقریبا انواع و اقسام ActionResultهای مهم و کاربردی ASP.NET MVC را میتوانید مشاهده کنید:
using System.Web.Mvc;
namespace MvcApplication2.Controllers
{
public class ActionResultsController : Controller
{
//http://localhost/actionresults/welcome
public string Welcome()
{
return "Hello, World";
}
//http://localhost/actionresults/index
public ActionResult Index() // or ContentResult
{
return Content("Hello, World");
}
//http://localhost/actionresults/SendMail
public void SendMail()
{
}
public ActionResult SendMailCompleted() // or EmptyResult
{
// do whatever
return new EmptyResult();
}
public ActionResult GetFile() // or FilePathResult
{
return File(Server.MapPath("~/content/site.css"), "text/css", "mySite.css");
}
public ActionResult UnauthorizedStatus() // or HttpStatusCodeResult/HttpUnauthorizedResult
{
return new HttpUnauthorizedResult("You need to login first.");
}
public ActionResult Status() // or HttpStatusCodeResult
{
return new HttpStatusCodeResult(501, "Server Error");
}
public ActionResult GetJavaScript() // or JavaScriptResult
{
return JavaScript("...JavaScript...");
}
public ActionResult GetJson() // or JsonResult
{
var obj = new { prop1 = 1, prop2 = "data" };
return Json(obj, JsonRequestBehavior.AllowGet);
}
public ActionResult RedirectTo() // or RedirectResult
{
return RedirectPermanent("http://www.site.com");
//return RedirectToAction("Home", "Index");
}
public ActionResult ShowView() // or ViewResult
{
return View();
}
}
}
چند نکته در این مثال وجود دارد:
1) مثلا متد GetJavaScript را درنظر بگیرید. در این متد خاص، چه بنویسید public ActionResult GetJavaScript یا بنویسید public JavaScriptResult GetJavaScript تفاوتی نمیکند. در سایر موارد هم به همین ترتیب است. علت را در تصویر سلسله مراتبی ActionResultها میتوان جستجو کرد. تمام این کلاسها نوعی ActionResult هستند و از یک کلاس پایه به ارث رسیدهاند.
2) مثلا ContentResult شبیه به همان Response.Write سابق ASP.NET عمل میکند. علت وجودی آن هم عدم وابستگی مستقیم به شیء Response و سادهتر سازی نوشتن آزمونهای واحد برای این نوع اکشن متدها است.
3) منهای متد آخری که نمایش داده شده (ShowView)، هیچکدام از متدهای دیگر نیازی به View متناظر ندارند. یعنی نیازی نیست تا روی متد کلیک راست کرده و Add view را انتخاب کنیم. چون در همین متد کنترلر، کار Response به پایان میرسد و مرحله بعدی ندارد. مثلا در حالت return File، یک فایل به درون مرورگر کاربر Flush خواهد شد و تمام.
4) متد Welcome و متد Index در اینجا به یک صورت تفسیر میشوند. به این معنا که اگر خروجی متد تعریف شده در یک کنترلر از نوع ActionResult نباشد، به صورت پیش فرض درون یک ContentResult محصور خواهد شد.
5) اگر خروجی متدی در اینجا از نوع void باشد، با ActionResult ایی به نام EmptyResult یکسان خواهد بود. بنابراین با متدهای SendMail و SendMailCompleted به یک نحو رفتار میگردد.
6) return Json یاد شده که خروجیاش از نوع JsonResultاست در پیاده سازیهای Ajax ایی کاربرد دارد.
7) جهت بازگرداندن حالت وضعیت 403 یا غیرمجاز میتوان از return new HttpUnauthorizedResult استفاده کرد.
8) یا جهت اعلام مشکلی در سمت سرور به کمک return new HttpStatusCodeResultکد ویژهای را میتوان به کاربر نمایش داد.
9) به کمک return RedirectToAction میتوان به یک کنترلر و متدی خاص در آن، کاربر را هدایت کرد.
و خلاصه اینکه تمام کارهایی را که پیشتر در ASP.NET Web forms ، مستقیما به کمک شیء Response انجام میدادید (Response.Write، Response.End، Response.Redirect و غیره)، اینبار به کمک یکی از ActionResultهای یاد شده انجام دهید تا بتوان بدون نیاز به راه اندازی یک وب سرور، برای متدهای کنترلرها آزمون واحد نوشت. برای مثال:
[TestMethod]
public void TestMethod1()
{
// Arrange
var controller = new ActionResultsController();
// Act
var result = controller.Index() as ContentResult;
// Assert
Assert.NotNull(result);
Assert.AreEqual( "Hello, World", result.Content);
}
هر برنامهی وبی، نیاز به کار با فرمهای وب را دارد و به همین جهت، AngularJS 2.0 به همراه دو نوع از فرمها است: فرمهای مبتنی بر قالبها و فرمهای مبتنی بر مدلها.
کار با فرمهای مبتنی بر قالبها سادهتر است؛ اما کنترل کمتری را بر روی مباحث اعتبارسنجی دادههای ورودی توسط کاربر، در اختیار ما قرار میدهند. اما فرمهای مبتنی بر مدلها هر چند به همراه اندکی کدنویسی بیشتر هستند، اما کنترل کاملی را جهت اعتبارسنجی ورودیهای کاربران، ارائه میدهند. در این قسمت فرمهای مبتنی بر قالبها (Template-driven forms) را بررسی میکنیم.
ساخت فرم مبتنی بر قالبهای ثبت یک محصول جدید
در ادامهی مثال این سری، میخواهیم به کاربران، امکان ثبت اطلاعات یک محصول جدید را نیز بدهیم. به همین جهت فایلهای جدید product-form.component.ts و product-form.component.html را به پوشهی App\products برنامه اضافه میکنیم (جهت تعریف کامپوننت فرم جدید به همراه قالب HTML آن).
الف) محتوای کامل product-form.component.html
ب) محتوای کامل product-form.component.ts
اکنون ریز جزئیات و تغییرات این دو فایل را قدم به قدم بررسی خواهیم کرد.
تا اینجا در فایل product-form.component.html یک فرم سادهی HTML ایی مبتنی بر بوت استرپ 3 را تهیه کردهایم. نکات ابتدایی آن، دقیقا مطابق است با مستندات بوت استرپ 3؛ از لحاظ تعریف form-horizontal و سپس ایجاد یک div با کلاس form-group و قرار دادن المانهایی با کلاسهای form-control در آن. همچنین برچسبهای تعریف شدهی با ویژگی for، در این المانها، جهت بالارفتن دسترسی پذیری به عناصر فرم، اضافه شدهاند. این مراحل در مورد تمام فرمهای استاندارد وب صادق هستند و نکتهی جدیدی ندارند.
در ادامه تعاریف AngularJS 2.0 را به این فرم اضافه کردهایم. در اینجا هر کدام از المانهای ورودی، تبدیل به Controlهای AngularJS 2.0 شدهاند. کلاس Control، خواص ویژهای را در اختیار ما قرار میدهد. برای مثال value یا مقدار این المان چیست؟ وضعیت touched و untouched آن چیست؟ (آیا کاربر فوکوس را به آن منتقل کردهاست یا خیر؟) آیا dirty است؟ (مقدار آن تغییر کردهاست؟) و یا شاید هم pristine است؟ (مقدار آن تغییری نکردهاست). علاوه بر اینها دارای خاصیت valid نیز میباشد (آیا اعتبارسنجی آن موفقیت آمیز است؟)؛ به همراه خاصیت errors که مشکلات اعتبارسنجی موجود را باز میگرداند.
در اینجا کلاس مفید دیگری به نام ControlGroup نیز درنظر گرفته شدهاست. برای مثال هر فرم، یک ControlGroup است (گروهی متشکل از کنترلها، در صفحه). البته میتوان یک فرم بزرگ را به چندین ControlGroup نیز تقسیم کرد. تمام خواصی که برای کلاس Control ذکر شدند، در مورد کلاس ControlGroup نیز صادق هستند. با این تفاوت که اینبار اگر به خاصیت valid آن مراجعه کردیم، یعنی تمام کنترلهای قرار گرفتهی در آن گروه معتبر هستند و نه صرفا یک تک کنترل خاص. به همین ترتیب خاصیت errors نیز تمام خطاهای اعتبارسنجی یک گروه را باز میگرداند.
هر دو کلاس Control و ControlGroup از کلاس پایهای به نام AbstractControl مشتق شدهاند و این کلاس پایه است که خواص مشترک یاد شده را به همراه دارد.
بنابراین برای کار سادهتر با یک فرم AngularJS 2.0، کل فرم را تبدیل به یک ControlGroup کرده و سپس هر کدام از المانهای ورودی را تبدیل به یک Control مجزا میکنیم. کار برقراری این ارتباط، با استفاده از دایرکتیو ویژهای به نام ngControl انجام میشود. بنابراین دایرکتیو ngControl، با نامی دلخواه و معین، به تمام المانهای ورودی، انتساب داده شدهاست.
هرچند در این مثال نام ngControlها با مقدار id هر کنترل یکسان درنظر گرفته شدهاست، اما ارتباطی بین این دو نیست. مقدار id جهت استفادهی در DOM کاربرد دارد و مقدار ngControl توسط AngularJS 2.0 استفاده میشود. جهت رسیدن به کدهایی یکدست، بهتر است این نامها را یکسان درنظر گرفت؛ اما هیچ الزامی هم ندارد.
برای بررسی جزئیات این اشیاء کنترل، در المان productName، یک متغیر محلی را به نام productName# تعریف کردهایم و آنرا به دایرکتیو ngControl انتساب دادهایم. این انتساب توسط ngForm انجام شدهاست. زمانیکه AngularJS 2.0 یک متغیر محلی تنظیم شدهی به ngForm را مشاهده میکند، آنرا به صورت خودکار به ngControl همان المان ورودی متصل میکند. سپس این متغیر محلی را به متد log ارسال کردهایم. این متد در کلاس کامپوننت جاری تعریف شدهاست و کار آن نمایش شیء Control جاری در کنسول developer tools مرورگر است.
همانطور که در تصویر مشاهده میکنید، عناصر یک شیء Control، در کنسول نمایش داده شدهاند و در اینجا بهتر میتوان خواصی مانند valid و امثال آنرا که به همراه این کنترل وجود دارند، مشاهده کرد. برای مثال خاصیت dirty آن true است چون مقدار آن المان ورودی، تغییر کردهاست.
بنابراین تا اینجا با استفاده از دایرکتیو ngControl، یک المان ورودی را به یک شیء Control متصل کردیم. همچنین نحوهی تعریف یک متغیر محلی را در المانی و سپس ارسال آن را به کلاس متناظر با کامپوننت فرم، نیز بررسی کردیم.
افزودن اعتبارسنجی به فرم ثبت محصولات
به کنترلهایی که به صورت فوق توسط ngControl ایجاد میشوند، اصطلاحا implicitly created controls میگویند؛ یا به عبارتی ایجاد آنها به صورت «ضمنی» توسط AngularJS 2.0 انجام میشود که نمونهای از آنرا در تصویر فوق نیز مشاهده کردید. این نوع کنترلهای ضمنی، امکانات اعتبارسنجی محدودی را در اختیار دارند؛ که تنها سه مورد هستند:
الف) required
ب) minlength
ج) maxlength
اینها ویژگیهای استاندارد اعتبارسنجی HTML 5 نیز هستند. نمونهای از اعمال این موارد را با افزودن ویژگی required، به المانهای فرم ثبت محصولات فوق، مشاهده میکنید.
سپس نیاز داریم تا خطاهای اعتبارسنجی را در مقابل هر المان ورودی نمایش دهیم.
پس از افزودن ویژگی required به یک المان، افزودن و نمایش خطاهای اعتبارسنجی، شامل سه مرحلهی زیر است:
الف) ایجاد یک div ساده جهت نمایش پیام خطای اعتبار سنجی
ب) افزودن یک متغیر محلی با # و تنظیم شدهی به ngForm، جهت دسترسی به شیء کنترل ایجاد شده
ج) استفاده از این متغیر محلی در دایرکتیو ساختاری ngIf* جهت دسترسی به خاصیت valid آن کنترل. بر مبنای مقدار این خاصیت است که تصمیم گرفته میشود، پیام اجباری بودن پر کردن فیلد نمایش داده شود یا خیر.
در اینجا یک سری کلاس بوت استرپ 3 هم جهت نمایش بهتر این پیام خطای اعتبارسنجی، اضافه شدهاند.
علت استفاده از خاصیت touched این است که اگر آنرا حذف کنیم، در اولین بار نمایش فرم، ذیل تمام المانهای ورودی، پیام اجباری بودن تکمیل آنها نمایش داده میشود. با استفاده از خاصیت touched، اگر کاربر به المانی مراجعه کرد و سپس آنرا تکمیل نکرد، آنگاه پیام خطای اعتبارسنجی را دریافت میکند.
بهبود شیوه نامهی پیش فرض المانهای ورودی اطلاعات در AngularJS 2.0
میخواهیم اگر اعتبارسنجی یک المان ورودی با شکست مواجه شد، یک حاشیهی قرمز، در اطراف آن نمایش داده شود. این مورد را با توجه به اینکه AngularJS 2.0، شیوه نامههای ویژهای را به صورت خودکار به المانها اضافه میکند، میتوان به صورت سراسری به تمام فرمها اضافه کرد. برای این منظور فایل app.component.css واقع در ریشهی پوشهی app را گشوده و تنظیمات ذیل را به آن اضافه کنید:
ویژگیهای اضافه شدهی در حالت شکست اعتبارسنجی؛ مانند ng-invalid
ویژگیهای اضافه شدهی در حالت موفقیت اعتبارسنجی؛ مانند ng-valid
مدیریت چندین ویژگی اعتبارسنجی یک المان با هم
گاهی از اوقات نیاز است برای یک المان ورودی، چندین نوع اعتبارسنجی مختلف را تعریف کرد. برای مثال فرض کنید که ویژگیهای required و همچنین minlength، برای نام محصول تنظیم شدهاند. در این حالت ذکر productName.valid خیلی عمومی است و هر دو حالت اجباری بودن فیلد و حداقل طول آنرا با هم به همراه دارد:
بنابراین در این حالت از روش ذیل استفاده میشود:
خاصیت errors نیز یکی دیگر از خواص شیء کنترل است. اگر نال بود، یعنی خطایی وجود ندارد و در غیراینصورت، به ازای هر نوع اعتبارسنجی تعریف شده، خواصی به آن اضافه میشوند. بنابراین ذکر productName.errors.required به این معنا است که آیا خاصیت errors، دارای کلیدی به نام required است؟ اگر بله، یعنی این فیلد هنوز پر نشدهاست.
همچنین چون در این حالت productName.touched نیاز است چندین بار تکرار شود، میتوان آنرا در یک div محصور کنندهی دو div مورد نیاز جهت نمایش خطاهای اعتبارسنجی قرار داد. به علاوه بررسی نال نبودن productName.errors نیز در div محصور کننده صورت گرفتهاست و دیگر نیازی نیست این بررسی را به ngIfهای داخلی اضافه کرد.
نکته 1
اگر علاقمند بودید تا جزئیات خاصیت errors را مشاهده کنید، آنرا میتوان توسط pipe توکاری به نام json به صورت موقت نمایش داد و بعد آنرا حذف کرد:
نکته 2
بجای ذکر مستقیم عدد سه در «minimum 3 characters»، میتوان این عدد را مستقیما از تعریف ویژگی minlength نیز استخراج کرد:
بررسی ngForm
شبیه به ngControl که یک المان ورودی را به یک کنترل AngularJS 2.0 متصل میکند، دایرکتیو دیگری نیز به نام ngForm وجود دارد که کل فرم را به شیء ControlGroup بایند میکند و برخلاف ngControl، نیازی به ذکر صریح آن وجود ندارد. هر زمانیکه AngularJS 2.0، المان استاندارد فرمی را در صفحه مشاهده میکند، این اتصالات را به صورت خودکار برقرار خواهد کرد.
ngForm دارای خاصیتی است به نام ngSumbit که از نوع EventEmitter است (نمونهای از آن را در مبحث کامپوننتهای تو در تو پیشتر ملاحظه کردهاید). بنابراین از آن میتوان جهت اتصال رخداد submit فرم، به متدی در کلاس کامپوننت خود، استفاده کرد. متد متصل به این رخداد، زمانی فراخوانی میشود که کاربر بر روی دکمهی submit کلیک کند:
همچنین در اینجا متغیر محلی f جهت دسترسی به شیء ControlGroup و ارسال آن به متد onSubmit تعریف شدهاست (شبیه به متغیرهای محلی دسترسی به ngControl که پیشتر جهت نمایش خطاهای اعتبارسنجی، اضافه کردیم).
پس از تعریف این رخداد و اتصال آن در قالب کامپوننت، اکنون میتوان متد onSubmit را در کلاس آن نیز اضافه کرد.
فعلا هدف از این متد، نمایش جزئیات شیء form دریافتی، در کنسول developer tools است.
غیرفعال کردن دکمهی submit در صورت وجود خطاهای اعتبارسنجی
در قسمت بررسی ngForm، یک متغیر محلی را به نام f ایجاد کردیم که به شیء ControlGroup فرم جاری اشاره میکند. از این متغیر و خاصیت valid آن میتوان با کمک property binding به خاصیت disabled یک دکمه، آنرا به صورت خودکار فعال یا غیرفعال کرد:
هر زمانیکه کل فرم از لحاظ اعتبارسنجی مشکلی نداشته باشد، دکمهی submit فعال میشود و برعکس.
نمایش فرم افزودن محصولات توسط سیستم Routing
با نحوهی تعریف مسیریابیها در قسمت قبل آشنا شدیم. برای نمایش فرم افزودن محصولات، میتوان تغییرات ذیل را به فایل app.component.ts اعمال کرد:
ابتدا به RouteConfig، مسیریابی کامپوننت فرم افزودن محصولات اضافه شدهاست. سپس ماژول این کلاس در ابتدای فایل import شده و در آخر routerLink آن به قالب سایت و منوی بالای سایت اضافه شدهاست.
اتصال المانهای فرم به مدلی جهت ارسال به سرور
برای اتصال المانهای فرم به یک مدل، این مدل را به صورت یک خاصیت عمومی، در سطح کلاس کامپوننت فرم، تعریف میکنیم:
اگر از اینترفیسی مانند IProduct که در قسمتهای قبل این سری تعریف شد، نیاز است شیء جدیدی ساخته شود، الزاما نیازی نیست تا یک کلاس جدید را از آن مشتق کرد و بعد متغیر new ClassName را تهیه کرد. در TypeScript میتوان به صورت خلاصه از syntax فوق نیز استفاده کرد.
پس از تعریف خاصیت productModel، اکنون کافی است با استفاده از two-way data binding، آنرا به المانهای فرم نسبت دهیم. برای مثال:
در اینجا با استفاده از ngModel و انقیاد دو طرفه، کار اتصال به خاصیت توضیحات شیء محصول انجام شدهاست. اکنون بلافاصله تغییرات اعمالی به فرم، به مدل متناظر منعکس میشود و برعکس. این ngModel را به تمام المانهای ورودی فرم متصل خواهیم کرد.
پس از تعریف یک چنین اتصالی، دیگر نیازی به مقدار دهی پارامتر onSubmit(f.form) نیست. زیرا شیء productModel، در متد onSumbit در دسترس است و این شیء همواره حاوی آخرین تغییرات اعمالی به المانهای فرم است.
پس از اینکه فرم را به مدل آن متصل کردیم، فایل product.service.ts را گشوده و متد جدید addProduct را به آن اضافه کنید:
کار این متد، ارسال شیء محصول به یک اکشن متد برنامهی ASP.NET MVC جاری است. با جزئیات کار با obsevables درمطلب «دریافت اطلاعات از سرور» پیشتر آشنا شدهایم.
نکتهی مهم اینجا است که content type پیش فرض ارسالی متد post آن، plain text است و در این حالت ASP.NET MVC شیء JSON دریافتی از کلاینت را پردازش نخواهد کرد. بنابراین نیاز است تا هدر content type را به صورت صریحی در اینجا ذکر نمود؛ در غیراینصورت در سمت سرور، شاهد نال بودن مقادیر دریافتی از کاربران خواهیم بود.
امضای سمت سرور متد دریافت اطلاعات از کاربر، چنین شکلی را دارد (تعریف شده در فایل Controllers\HomeController.cs):
اشیاء هدرها و تنظیمات درخواست، در متد addProduct سرویس ProductService، در ماژولهای ذیل تعریف شدهاند که باید به ابتدای فایل product.service.ts اضافه شوند:
پس از تعریف متد addProduct در سرویس ProductService، اکنون با استفاده از ترزیق این سرویس به سازندهی کلاس فرم ثبت یک محصول جدید، میتوان متد this._productService.addProduct را جهت ارسال productModel به سمت سرور، در متد onSubmit فراخوانی کرد:
همانطور که ذکر شد، از آنجائیکه شیء productModel حاوی آخرین تغییرات اعمالی توسط کاربر است، اکنون میتوان پارامتر form متد onSubmit را حذف کرد.
در اینجا پس از فراخوانی متد addProduct، متد subscribe، در انتهای زنجیره، فراخوانی شدهاست. کار آن هدایت کاربر به صفحهی نمایش لیست محصولات است. در اینجا this._router از طریق تزریق وابستگیهای سرویس مسیریاب به سازندهی کلاس، تامین شدهاست. نمونهی آنرا در قسمت «افزودن دکمهی back با کدنویسی» مربوطه به مطلب آشنایی با مسیریابی، پیشتر مطالعه کردهاید.
کدهای کامل این قسمت را از اینجا میتوانید دریافت کنید: MVC5Angular2.part10.zip
خلاصهی بحث
فرمهای template driven در AngularJS 2.0 به این نحو طراحی میشوند:
1) ابتدا فرم HTML را به حالت معمولی آن طراحی میکنیم؛ با تمام المانهای آن.
2) به تمام المانهای فرم، دیراکتیو ngControl را متصل میکنیم، تا AngularJS 2.0 آنرا تبدیل به یک کنترل خاص خودش کند. کنترلی که دارای خواصی مانند valid و touched است.
3) سپس برای دسترسی به این کنترل ایجاد شدهی به صورت ضمنی، یک متغیر محلی آغاز شدهی با # را به تمام المانها اضافه میکنیم.
4) اعتبارسنجیهایی را مانند required به المانهای فرم اضافه میکنیم.
5) از متغیر محلی تعریف شده و ngIf* برای بررسی خواصی مانند valid و touched برای نمایش خطاهای اعتبارسنجی کمک گرفته میشود.
6) پس از تعریف فرم، تعریف ngControlها، تعریف متغیر محلی شروع شدهی با # و افزودن خطاهای اعتبارسنجی، اکنون نوبت به ارسال این اطلاعات به سرور است. بنابراین رخداد ngSubmit را باید به متدی در کلاس کامپوننت جاری متصل کرد.
7) اکنون که با کلیک بر روی دکمهی submit فرم، متد onSubmit متصل به ngSubmit فراخوانی میشود، نیاز است بین المانهای فرم HTML و کلاس کامپوننت، ارتباط برقرار کرد. اینکار را توسط two-way data binding و تعریف ngModel بر روی تمام المانهای فرم، انجام میدهیم. این ngModelها، به یک خاصیت عمومی که متناظر است با وهلهای از شیء مدل فرم، متصل هستند. بنابراین این مدل، در هر لحظه، بیانگر آخرین تغییرات کاربر است و از آن میتوان برای ارسال اطلاعات به سرور استفاده کرد.
8) پس از اتصال فرم به کلاس متناظر با آن، اکنون سرویس محصولات را تکمیل کرده و به آن متد HTTP Post را جهت ارسال اطلاعات سمت کاربر، به سرور، اضافه میکنیم. در اینجا نکتهی مهم، تنظیم content type ارسالی به سمت سرور است. در غیراینصورت فریم ورک سمت سرور قادر به تشخیص JSON بودن این اطلاعات نخواهد شد.
کار با فرمهای مبتنی بر قالبها سادهتر است؛ اما کنترل کمتری را بر روی مباحث اعتبارسنجی دادههای ورودی توسط کاربر، در اختیار ما قرار میدهند. اما فرمهای مبتنی بر مدلها هر چند به همراه اندکی کدنویسی بیشتر هستند، اما کنترل کاملی را جهت اعتبارسنجی ورودیهای کاربران، ارائه میدهند. در این قسمت فرمهای مبتنی بر قالبها (Template-driven forms) را بررسی میکنیم.
ساخت فرم مبتنی بر قالبهای ثبت یک محصول جدید
در ادامهی مثال این سری، میخواهیم به کاربران، امکان ثبت اطلاعات یک محصول جدید را نیز بدهیم. به همین جهت فایلهای جدید product-form.component.ts و product-form.component.html را به پوشهی App\products برنامه اضافه میکنیم (جهت تعریف کامپوننت فرم جدید به همراه قالب HTML آن).
الف) محتوای کامل product-form.component.html
<form #f="ngForm" (ngSubmit)="onSubmit(f.form)"> <div class="panel panel-default"> <div class="panel-heading"> <h3 class="panel-title"> Add Product </h3> </div> <div class="panel-body form-horizontal"> <div class="form-group"> <label for="productName" class="col col-md-2 control-label">Name</label> <div class="controls col col-md-10"> <input ngControl="productName" id="productName" required #productName="ngForm" (change)="log(productName)" minlength="3" type="text" class="form-control" [(ngModel)]="productModel.productName"/> <div *ngIf="productName.touched && productName.errors"> <label class="text-danger" *ngIf="productName.errors.required"> Name is required. </label> <label class="text-danger" *ngIf="productName.errors.minlength"> Name should be minimum {{ productName.errors.minlength.requiredLength }} characters. </label> </div> </div> </div> <div class="form-group"> <label for="productCode" class="col col-md-2 control-label">Code</label> <div class="controls col col-md-10"> <input ngControl="productCode" id="productCode" required #productCode="ngForm" type="text" class="form-control" [(ngModel)]="productModel.productCode"/> <label class="text-danger" *ngIf="productCode.touched && !productCode.valid"> Code is required. </label> </div> </div> <div class="form-group"> <label for="releaseDate" class="col col-md-2 control-label">Release Date</label> <div class="controls col col-md-10"> <input ngControl="releaseDate" id="releaseDate" required #releaseDate="ngForm" type="text" class="form-control" [(ngModel)]="productModel.releaseDate"/> <label class="text-danger" *ngIf="releaseDate.touched && !releaseDate.valid"> Release Date is required. </label> </div> </div> <div class="form-group"> <label for="price" class="col col-md-2 control-label">Price</label> <div class="controls col col-md-10"> <input ngControl="price" id="price" required #price="ngForm" type="text" class="form-control" [(ngModel)]="productModel.price"/> <label class="text-danger" *ngIf="price.touched && !price.valid"> Price is required. </label> </div> </div> <div class="form-group"> <label for="description" class="col col-md-2 control-label">Description</label> <div class="controls col col-md-10"> <textarea ngControl="description" id="description" required #description="ngForm" rows="10" type="text" class="form-control" [(ngModel)]="productModel.description"></textarea> <label class="text-danger" *ngIf="description.touched && !description.valid"> Description is required. </label> </div> </div> <div class="form-group"> <label for="imageUrl" class="col col-md-2 control-label">Image</label> <div class="controls col col-md-10"> <input ngControl="imageUrl" id="imageUrl" required #imageUrl="ngForm" type="text" class="form-control" [(ngModel)]="productModel.imageUrl"/> <label class="text-danger" *ngIf="imageUrl.touched && !imageUrl.valid"> Image is required. </label> </div> </div> </div> <footer class="panel-footer"> <button [disabled]="!f.valid" type="submit" class="btn btn-primary"> Submit </button> </footer> </div> </form>
ب) محتوای کامل product-form.component.ts
import { Component } from 'angular2/core'; import { Router } from 'angular2/router'; import { IProduct } from './product'; import { ProductService } from './product.service'; @Component({ //selector: 'product-form', templateUrl: 'app/products/product-form.component.html' }) export class ProductFormComponent { productModel = <IProduct>{}; // creates an empty object of an interface constructor(private _productService: ProductService, private _router: Router) { } log(productName): void { console.log(productName); } onSubmit(form): void { console.log(form); console.log(this.productModel); this._productService.addProduct(this.productModel) .subscribe((product: IProduct) => { console.log(`ID: ${product.productId}`); this._router.navigate(['Products']); }); } }
اکنون ریز جزئیات و تغییرات این دو فایل را قدم به قدم بررسی خواهیم کرد.
تا اینجا در فایل product-form.component.html یک فرم سادهی HTML ایی مبتنی بر بوت استرپ 3 را تهیه کردهایم. نکات ابتدایی آن، دقیقا مطابق است با مستندات بوت استرپ 3؛ از لحاظ تعریف form-horizontal و سپس ایجاد یک div با کلاس form-group و قرار دادن المانهایی با کلاسهای form-control در آن. همچنین برچسبهای تعریف شدهی با ویژگی for، در این المانها، جهت بالارفتن دسترسی پذیری به عناصر فرم، اضافه شدهاند. این مراحل در مورد تمام فرمهای استاندارد وب صادق هستند و نکتهی جدیدی ندارند.
در ادامه تعاریف AngularJS 2.0 را به این فرم اضافه کردهایم. در اینجا هر کدام از المانهای ورودی، تبدیل به Controlهای AngularJS 2.0 شدهاند. کلاس Control، خواص ویژهای را در اختیار ما قرار میدهد. برای مثال value یا مقدار این المان چیست؟ وضعیت touched و untouched آن چیست؟ (آیا کاربر فوکوس را به آن منتقل کردهاست یا خیر؟) آیا dirty است؟ (مقدار آن تغییر کردهاست؟) و یا شاید هم pristine است؟ (مقدار آن تغییری نکردهاست). علاوه بر اینها دارای خاصیت valid نیز میباشد (آیا اعتبارسنجی آن موفقیت آمیز است؟)؛ به همراه خاصیت errors که مشکلات اعتبارسنجی موجود را باز میگرداند.
<div class="form-group"> <label for="description" class="col col-md-2 control-label">Description</label> <div class="controls col col-md-10"> <textarea ngControl="description" id="description" required #description="ngForm" rows="10" type="text" class="form-control" [(ngModel)]="productModel.description"></textarea> <label class="text-danger" *ngIf="description.touched && !description.valid"> Description is required. </label> </div> </div>
هر دو کلاس Control و ControlGroup از کلاس پایهای به نام AbstractControl مشتق شدهاند و این کلاس پایه است که خواص مشترک یاد شده را به همراه دارد.
بنابراین برای کار سادهتر با یک فرم AngularJS 2.0، کل فرم را تبدیل به یک ControlGroup کرده و سپس هر کدام از المانهای ورودی را تبدیل به یک Control مجزا میکنیم. کار برقراری این ارتباط، با استفاده از دایرکتیو ویژهای به نام ngControl انجام میشود. بنابراین دایرکتیو ngControl، با نامی دلخواه و معین، به تمام المانهای ورودی، انتساب داده شدهاست.
هرچند در این مثال نام ngControlها با مقدار id هر کنترل یکسان درنظر گرفته شدهاست، اما ارتباطی بین این دو نیست. مقدار id جهت استفادهی در DOM کاربرد دارد و مقدار ngControl توسط AngularJS 2.0 استفاده میشود. جهت رسیدن به کدهایی یکدست، بهتر است این نامها را یکسان درنظر گرفت؛ اما هیچ الزامی هم ندارد.
برای بررسی جزئیات این اشیاء کنترل، در المان productName، یک متغیر محلی را به نام productName# تعریف کردهایم و آنرا به دایرکتیو ngControl انتساب دادهایم. این انتساب توسط ngForm انجام شدهاست. زمانیکه AngularJS 2.0 یک متغیر محلی تنظیم شدهی به ngForm را مشاهده میکند، آنرا به صورت خودکار به ngControl همان المان ورودی متصل میکند. سپس این متغیر محلی را به متد log ارسال کردهایم. این متد در کلاس کامپوننت جاری تعریف شدهاست و کار آن نمایش شیء Control جاری در کنسول developer tools مرورگر است.
<input ngControl="productName" id="productName" required #productName="ngForm" (change)="log(productName)" minlength="3" type="text" class="form-control" [(ngModel)]="productModel.productName"/>
همانطور که در تصویر مشاهده میکنید، عناصر یک شیء Control، در کنسول نمایش داده شدهاند و در اینجا بهتر میتوان خواصی مانند valid و امثال آنرا که به همراه این کنترل وجود دارند، مشاهده کرد. برای مثال خاصیت dirty آن true است چون مقدار آن المان ورودی، تغییر کردهاست.
بنابراین تا اینجا با استفاده از دایرکتیو ngControl، یک المان ورودی را به یک شیء Control متصل کردیم. همچنین نحوهی تعریف یک متغیر محلی را در المانی و سپس ارسال آن را به کلاس متناظر با کامپوننت فرم، نیز بررسی کردیم.
افزودن اعتبارسنجی به فرم ثبت محصولات
به کنترلهایی که به صورت فوق توسط ngControl ایجاد میشوند، اصطلاحا implicitly created controls میگویند؛ یا به عبارتی ایجاد آنها به صورت «ضمنی» توسط AngularJS 2.0 انجام میشود که نمونهای از آنرا در تصویر فوق نیز مشاهده کردید. این نوع کنترلهای ضمنی، امکانات اعتبارسنجی محدودی را در اختیار دارند؛ که تنها سه مورد هستند:
الف) required
ب) minlength
ج) maxlength
اینها ویژگیهای استاندارد اعتبارسنجی HTML 5 نیز هستند. نمونهای از اعمال این موارد را با افزودن ویژگی required، به المانهای فرم ثبت محصولات فوق، مشاهده میکنید.
سپس نیاز داریم تا خطاهای اعتبارسنجی را در مقابل هر المان ورودی نمایش دهیم.
<textarea ngControl="description" id="description" required #description="ngForm" rows="10" type="text" class="form-control"></textarea> <div class="alert alert-danger" *ngIf="description.touched && !description.valid"> Description is required. </div>
الف) ایجاد یک div ساده جهت نمایش پیام خطای اعتبار سنجی
ب) افزودن یک متغیر محلی با # و تنظیم شدهی به ngForm، جهت دسترسی به شیء کنترل ایجاد شده
ج) استفاده از این متغیر محلی در دایرکتیو ساختاری ngIf* جهت دسترسی به خاصیت valid آن کنترل. بر مبنای مقدار این خاصیت است که تصمیم گرفته میشود، پیام اجباری بودن پر کردن فیلد نمایش داده شود یا خیر.
در اینجا یک سری کلاس بوت استرپ 3 هم جهت نمایش بهتر این پیام خطای اعتبارسنجی، اضافه شدهاند.
علت استفاده از خاصیت touched این است که اگر آنرا حذف کنیم، در اولین بار نمایش فرم، ذیل تمام المانهای ورودی، پیام اجباری بودن تکمیل آنها نمایش داده میشود. با استفاده از خاصیت touched، اگر کاربر به المانی مراجعه کرد و سپس آنرا تکمیل نکرد، آنگاه پیام خطای اعتبارسنجی را دریافت میکند.
بهبود شیوه نامهی پیش فرض المانهای ورودی اطلاعات در AngularJS 2.0
میخواهیم اگر اعتبارسنجی یک المان ورودی با شکست مواجه شد، یک حاشیهی قرمز، در اطراف آن نمایش داده شود. این مورد را با توجه به اینکه AngularJS 2.0، شیوه نامههای ویژهای را به صورت خودکار به المانها اضافه میکند، میتوان به صورت سراسری به تمام فرمها اضافه کرد. برای این منظور فایل app.component.css واقع در ریشهی پوشهی app را گشوده و تنظیمات ذیل را به آن اضافه کنید:
.ng-touched.ng-invalid{ border: 1px solid red; }
ویژگیهای اضافه شدهی در حالت شکست اعتبارسنجی؛ مانند ng-invalid
ویژگیهای اضافه شدهی در حالت موفقیت اعتبارسنجی؛ مانند ng-valid
مدیریت چندین ویژگی اعتبارسنجی یک المان با هم
گاهی از اوقات نیاز است برای یک المان ورودی، چندین نوع اعتبارسنجی مختلف را تعریف کرد. برای مثال فرض کنید که ویژگیهای required و همچنین minlength، برای نام محصول تنظیم شدهاند. در این حالت ذکر productName.valid خیلی عمومی است و هر دو حالت اجباری بودن فیلد و حداقل طول آنرا با هم به همراه دارد:
<div class="alert alert-danger" *ngIf="productName.touched && !productName.valid"> Name is required. </div>
<div *ngIf="productName.touched && productName.errors"> <div class="alert alert-danger" *ngIf="productName.errors.required"> Name is required. </div> <div class="alert alert-danger" *ngIf="productName.errors.minlength"> Name should be minimum 3 characters. </div> </div>
همچنین چون در این حالت productName.touched نیاز است چندین بار تکرار شود، میتوان آنرا در یک div محصور کنندهی دو div مورد نیاز جهت نمایش خطاهای اعتبارسنجی قرار داد. به علاوه بررسی نال نبودن productName.errors نیز در div محصور کننده صورت گرفتهاست و دیگر نیازی نیست این بررسی را به ngIfهای داخلی اضافه کرد.
نکته 1
اگر علاقمند بودید تا جزئیات خاصیت errors را مشاهده کنید، آنرا میتوان توسط pipe توکاری به نام json به صورت موقت نمایش داد و بعد آنرا حذف کرد:
<div *ngIf="productName.touched && productName.errors"> {{ productName.errors | json }}
نکته 2
بجای ذکر مستقیم عدد سه در «minimum 3 characters»، میتوان این عدد را مستقیما از تعریف ویژگی minlength نیز استخراج کرد:
Name should be minimum {{ productName.errors.minlength.requiredLength }} characters.
بررسی ngForm
شبیه به ngControl که یک المان ورودی را به یک کنترل AngularJS 2.0 متصل میکند، دایرکتیو دیگری نیز به نام ngForm وجود دارد که کل فرم را به شیء ControlGroup بایند میکند و برخلاف ngControl، نیازی به ذکر صریح آن وجود ندارد. هر زمانیکه AngularJS 2.0، المان استاندارد فرمی را در صفحه مشاهده میکند، این اتصالات را به صورت خودکار برقرار خواهد کرد.
ngForm دارای خاصیتی است به نام ngSumbit که از نوع EventEmitter است (نمونهای از آن را در مبحث کامپوننتهای تو در تو پیشتر ملاحظه کردهاید). بنابراین از آن میتوان جهت اتصال رخداد submit فرم، به متدی در کلاس کامپوننت خود، استفاده کرد. متد متصل به این رخداد، زمانی فراخوانی میشود که کاربر بر روی دکمهی submit کلیک کند:
<form #f="ngForm" (ngSubmit)="onSubmit(f.form)">
پس از تعریف این رخداد و اتصال آن در قالب کامپوننت، اکنون میتوان متد onSubmit را در کلاس آن نیز اضافه کرد.
onSubmit(form): void { console.log(form); }
غیرفعال کردن دکمهی submit در صورت وجود خطاهای اعتبارسنجی
در قسمت بررسی ngForm، یک متغیر محلی را به نام f ایجاد کردیم که به شیء ControlGroup فرم جاری اشاره میکند. از این متغیر و خاصیت valid آن میتوان با کمک property binding به خاصیت disabled یک دکمه، آنرا به صورت خودکار فعال یا غیرفعال کرد:
<button [disabled]="!f.valid" type="submit" class="btn btn-primary"> Submit </button>
نمایش فرم افزودن محصولات توسط سیستم Routing
با نحوهی تعریف مسیریابیها در قسمت قبل آشنا شدیم. برای نمایش فرم افزودن محصولات، میتوان تغییرات ذیل را به فایل app.component.ts اعمال کرد:
//same as before... import { ProductFormComponent } from './products/product-form.component'; @Component({ //same as before… template: ` //same as before… <li><a [routerLink]="['AddProduct']">Add Product</a></li> //same as before… `, //same as before… }) @RouteConfig([ //same as before… { path: '/addproduct', name: 'AddProduct', component: ProductFormComponent } ]) //same as before...
اتصال المانهای فرم به مدلی جهت ارسال به سرور
برای اتصال المانهای فرم به یک مدل، این مدل را به صورت یک خاصیت عمومی، در سطح کلاس کامپوننت فرم، تعریف میکنیم:
productModel = <IProduct>{}; // creates an empty object of an interface
پس از تعریف خاصیت productModel، اکنون کافی است با استفاده از two-way data binding، آنرا به المانهای فرم نسبت دهیم. برای مثال:
<textarea ngControl="description" id="description" required #description="ngForm" rows="10" type="text" class="form-control" [(ngModel)]="productModel.description"></textarea>
پس از تعریف یک چنین اتصالی، دیگر نیازی به مقدار دهی پارامتر onSubmit(f.form) نیست. زیرا شیء productModel، در متد onSumbit در دسترس است و این شیء همواره حاوی آخرین تغییرات اعمالی به المانهای فرم است.
پس از اینکه فرم را به مدل آن متصل کردیم، فایل product.service.ts را گشوده و متد جدید addProduct را به آن اضافه کنید:
addProduct(product: IProduct): Observable<IProduct> { let headers = new Headers({ 'Content-Type': 'application/json' }); // for ASP.NET MVC let options = new RequestOptions({ headers: headers }); return this._http.post(this._addProductUrl, JSON.stringify(product), options) .map((response: Response) => <IProduct>response.json()) .do(data => console.log("Product: " + JSON.stringify(data))) .catch(this.handleError); }
نکتهی مهم اینجا است که content type پیش فرض ارسالی متد post آن، plain text است و در این حالت ASP.NET MVC شیء JSON دریافتی از کلاینت را پردازش نخواهد کرد. بنابراین نیاز است تا هدر content type را به صورت صریحی در اینجا ذکر نمود؛ در غیراینصورت در سمت سرور، شاهد نال بودن مقادیر دریافتی از کاربران خواهیم بود.
امضای سمت سرور متد دریافت اطلاعات از کاربر، چنین شکلی را دارد (تعریف شده در فایل Controllers\HomeController.cs):
[HttpPost] public ActionResult AddProduct(Product product) {
اشیاء هدرها و تنظیمات درخواست، در متد addProduct سرویس ProductService، در ماژولهای ذیل تعریف شدهاند که باید به ابتدای فایل product.service.ts اضافه شوند:
import { Headers, RequestOptions } from 'angular2/http';
پس از تعریف متد addProduct در سرویس ProductService، اکنون با استفاده از ترزیق این سرویس به سازندهی کلاس فرم ثبت یک محصول جدید، میتوان متد this._productService.addProduct را جهت ارسال productModel به سمت سرور، در متد onSubmit فراخوانی کرد:
//Same as before… import { IProduct } from './product'; import { ProductService } from './product.service'; @Component({ //Same as before… }) export class ProductFormComponent { productModel = <IProduct>{}; // creates an empty object of an interface constructor(private _productService: ProductService, private _router: Router) { } //Same as before… onSubmit(form): void { console.log(form); console.log(this.productModel); this._productService.addProduct(this.productModel) .subscribe((product: IProduct) => { console.log(`ID: ${product.productId}`); this._router.navigate(['Products']); }); } }
در اینجا پس از فراخوانی متد addProduct، متد subscribe، در انتهای زنجیره، فراخوانی شدهاست. کار آن هدایت کاربر به صفحهی نمایش لیست محصولات است. در اینجا this._router از طریق تزریق وابستگیهای سرویس مسیریاب به سازندهی کلاس، تامین شدهاست. نمونهی آنرا در قسمت «افزودن دکمهی back با کدنویسی» مربوطه به مطلب آشنایی با مسیریابی، پیشتر مطالعه کردهاید.
کدهای کامل این قسمت را از اینجا میتوانید دریافت کنید: MVC5Angular2.part10.zip
خلاصهی بحث
فرمهای template driven در AngularJS 2.0 به این نحو طراحی میشوند:
1) ابتدا فرم HTML را به حالت معمولی آن طراحی میکنیم؛ با تمام المانهای آن.
2) به تمام المانهای فرم، دیراکتیو ngControl را متصل میکنیم، تا AngularJS 2.0 آنرا تبدیل به یک کنترل خاص خودش کند. کنترلی که دارای خواصی مانند valid و touched است.
3) سپس برای دسترسی به این کنترل ایجاد شدهی به صورت ضمنی، یک متغیر محلی آغاز شدهی با # را به تمام المانها اضافه میکنیم.
4) اعتبارسنجیهایی را مانند required به المانهای فرم اضافه میکنیم.
5) از متغیر محلی تعریف شده و ngIf* برای بررسی خواصی مانند valid و touched برای نمایش خطاهای اعتبارسنجی کمک گرفته میشود.
6) پس از تعریف فرم، تعریف ngControlها، تعریف متغیر محلی شروع شدهی با # و افزودن خطاهای اعتبارسنجی، اکنون نوبت به ارسال این اطلاعات به سرور است. بنابراین رخداد ngSubmit را باید به متدی در کلاس کامپوننت جاری متصل کرد.
7) اکنون که با کلیک بر روی دکمهی submit فرم، متد onSubmit متصل به ngSubmit فراخوانی میشود، نیاز است بین المانهای فرم HTML و کلاس کامپوننت، ارتباط برقرار کرد. اینکار را توسط two-way data binding و تعریف ngModel بر روی تمام المانهای فرم، انجام میدهیم. این ngModelها، به یک خاصیت عمومی که متناظر است با وهلهای از شیء مدل فرم، متصل هستند. بنابراین این مدل، در هر لحظه، بیانگر آخرین تغییرات کاربر است و از آن میتوان برای ارسال اطلاعات به سرور استفاده کرد.
8) پس از اتصال فرم به کلاس متناظر با آن، اکنون سرویس محصولات را تکمیل کرده و به آن متد HTTP Post را جهت ارسال اطلاعات سمت کاربر، به سرور، اضافه میکنیم. در اینجا نکتهی مهم، تنظیم content type ارسالی به سمت سرور است. در غیراینصورت فریم ورک سمت سرور قادر به تشخیص JSON بودن این اطلاعات نخواهد شد.
jQuery سالها به عنوان جزء اصلی توسعهی برنامههای وب مطرح بودهاست و برای بسیاری از توسعه دهندگان وب، یک پیشنیاز پیشفرض محسوب میشود؛ سادهاست، قابل فهم است و به آن اطمینان داریم. زمانیکه از آن استفاده میکنیم دیگر نیازی نیست تا آنچنان به DOM، باگهای مرورگرها و یا رفتارهای متفاوت آنها فکر کنیم. jQuery تمام این مشکلات را برای ما حل میکند. اما ... اگر روزی باگی در jQuery وجود داشت، نیاز به امکاناتی بود که هنوز در jQuery ظاهر نشدهاند و یا حتی اجازهی استفادهی از jQuery را نداشته باشیم، در این حالت ... وحشت زده و تقریبا بدون هیچ نوع آمادگی به نظر خواهیم رسید.
خالق جیکوئری (John Resig)، این کتابخانه را در سالهای 2006 زمانیکه Internet Explorer نگارشهای 6 و 7 بیش از 60 درصد بازار مرورگرها را به خود اختصاص داده بودند، ارائه داد. بله؛ در آْن زمان JavaScript Web API بسیار خام، پایداری مرورگرها بسیار پایین و تطابق با استانداردهای وب در بین مرورگرهای مختلف نیز بسیار پایین بود. بنابراین علت محبوبیت کتابخانهای که در این شرایط، تجربهی کاری یکدستی را در بین مرورگرهای مختلف ارائه میداد، کاملا واضح بود. اما ... اکنون سال 2018 است و حتی مایکروسافت هم دیگر از نگارشهای مختلف IE پشتیبانی نمیکند. DOM API موجود در مرورگرهای مدرن بسیار توانمند شدهاند و در بین انواع و اقسام آنها یکدست عمل میکنند. حتی اگر دلیل استفادهی از jQuery ایجاد سادهتر حلقهها بر روی اشیاء جاوا اسکریپتی باشد (رفع کمبودهای جاوا اسکریپت)، از زمان IE 9 به بعد، متدهای forEach و Object.keys به صورت توکار در جاوا اسکریپت وجود دارند و یا اگر نیاز به inArray.$ داشته باشید، متد Array.prototype.indexOf مدتها است که جزئی از ES5 است. به همین جهت است که این روزها اخباری را مانند «GitHub نیز جیکوئری را کنار گذاشت» زیاد میشنوید. نه فقط کنار گذاشتن jQuery یک وابستگی ثالث را از برنامه حذف میکند، بلکه کار مستقیم با native API مرورگرها همواره به مراتب سریعتر است از کتابخانههایی که سطح بالایی از abstraction آنها را ارائه میدهند.
یافتن عناصر توسط JavaScript خالص
زمانیکه نیاز به انتخاب عناصری در صفحه باشند، بلافاصله ذهن ما به سمت ('myElement#')$ و ('myElement.')$ جیکوئری، معطوف میشود. اما ... این روزها برای انجام این نوع کارها واقعا نیازی به jQuery نیست!
یافتن عناصر بر اساس ID آنها
اگر بخواهیم این شیء div را بر اساس ID آن در صفحه بیابیم، روش کار آن با jQuery به صورت زیر است:
در اینجا این ID selector string، یک استاندارد W3C CSS1 است.
انجام این کار توسط web API و یا همان JavaScript خالص، چنین شکلی را دارد:
و جالب است بدانید این روش از زمان IE 5.5 وجود داشتهاست.
روش دیگر انجام اینکار با JavaScript به صورت زیر است:
این روش و متد querySelector که بسیار شبیه به نمونهی جیکوئری ارائه شدهاست، از زمان IE 8.0 قابل استفادهاست.
در هر دو حالت، خروجی مقایسهی ذیل، true است:
یافتن عناصر بر اساس کلاسهای CSS
با جیکوئری:
با جاوا اسکریپت خالص از زمان IE 8.0
و یا توسط querySelectorAll که شبیه به نمونهی jQuery است و نیاز به پیشوند . را دارد:
در هر دو حالت، خروجی بازگشت داده شده، یک آرایه است:
یافتن عناصر بر اساس تگهای عناصر
با جیکوئری:
با جاوا اسکریپت:
و یا
در تمام این حالات، خروجی ارائه شده یک آرایه است:
یافتن عناصر بر اساس کلاس نماها (Pseudo-classes)
Pseudo-classes از زمان ابتداییترین پیشنویس استاندارد CSS وجود داشتهاند. برای مثال visited: یک Pseudo-classes است و به لینکهای بازدید شدهی توسط کاربر اشاره میکند و یا focus: به المانی اشاره میکند که هم اکنون دارای focus است.
در این مثال اگر بخواهیم تکست باکسی را بیابیم که دارای focus است، روش جیکوئری آن به صورت زیر است:
و روش جاوا اسکریپتی آن به این صورت:
کاری که در اینجا انجام شده ترکیب یک tag name و یک pseudo-class modifier است که جزئی از استاندارد CSS میباشد. بنابراین روش جیکوئری، چیزی بیشتر از انتقال این استاندارد به توابع بومی مرورگر نیست.
یافتن عناصر بر اساس ارتباط والد و فرزندی آنها
یافتن والدها:
روش یافتن والد anchor tag در جیکوئری توسط متد parent؛ با فرض اینکه a$ به شیء anchor اشاره میکند:
و در جاوا اسکریپت توسط خاصیت parentNode:
یافتن فرزندان:
در جیکوئری:
و برای یافتن فرزندان یک المان توسط CSS 2 child selectors:
خروجی این کوئری، المانهای a و p هستند و یا اگر فقط بخواهیم pها را انتخاب کنیم:
روش دیگر انجام اینکار استفاده از خاصیت childNodes یک المان است:
البته این خاصیت آرایهای، Text و Comments را هم علاوه بر عناصر بازگشت میدهد. البته اگر میخواهید آنها را حذف کنید، از خاصیت children استفاده کنید:
و یا یافتن تمام المانهای anchor ذیل المانی با Id مساوی myParent:
جستجوی عناصر با صرفنظر کردن از بعضی از آنها
در این مثال گزینهی دوم دارای class مساوی active است. اگر بخواهیم تمام liهایی را که دارای این کلاس نیستند، انتخاب کنیم، در جیکوئری خواهیم داشت:
و در جاوا اسکریپت:
هرچند JavaScript دارای متد not جیکوئری نیست، اما میتوان از W3C CSS3 negation pseudo-class بجای آن استفاده کرد. مزیت آن، استاندارد بودن و عدم نیاز به کتابخانهای ثالث برای تدارک آن است.
انتخاب چندین المان با هم
در اینجا میخواهیم المانهای link-container، my-name و لیست مرتب شده را بدون نوشتن حلقهای انتخاب کنیم. روش انجام اینکار در jQuery به صورت زیر است:
و در جاوا اسکریپت خواهیم داشت:
یافتن گروهی از المانها بر اساس نوع آنها:
در اینجا تمام المانهای ورودی از نوع <"button type="submit> و <"input type="submit> را بازگشت میدهد.
جایگزین کردن $ جیکوئری با جاوا اسکریپت
تا اینجا حتما به شباهت کدهای خالص جاوا اسکریپت و jQuery دقت کردهاید. اگر بخواهیم برای $ جیکوئری، یک معادل جاوا اسکریپتی تهیه کنیم، به قطعه کد زیر خواهیم رسید:
و نحوهی استفادهی از آن نیز همانند قبل است:
خالق جیکوئری (John Resig)، این کتابخانه را در سالهای 2006 زمانیکه Internet Explorer نگارشهای 6 و 7 بیش از 60 درصد بازار مرورگرها را به خود اختصاص داده بودند، ارائه داد. بله؛ در آْن زمان JavaScript Web API بسیار خام، پایداری مرورگرها بسیار پایین و تطابق با استانداردهای وب در بین مرورگرهای مختلف نیز بسیار پایین بود. بنابراین علت محبوبیت کتابخانهای که در این شرایط، تجربهی کاری یکدستی را در بین مرورگرهای مختلف ارائه میداد، کاملا واضح بود. اما ... اکنون سال 2018 است و حتی مایکروسافت هم دیگر از نگارشهای مختلف IE پشتیبانی نمیکند. DOM API موجود در مرورگرهای مدرن بسیار توانمند شدهاند و در بین انواع و اقسام آنها یکدست عمل میکنند. حتی اگر دلیل استفادهی از jQuery ایجاد سادهتر حلقهها بر روی اشیاء جاوا اسکریپتی باشد (رفع کمبودهای جاوا اسکریپت)، از زمان IE 9 به بعد، متدهای forEach و Object.keys به صورت توکار در جاوا اسکریپت وجود دارند و یا اگر نیاز به inArray.$ داشته باشید، متد Array.prototype.indexOf مدتها است که جزئی از ES5 است. به همین جهت است که این روزها اخباری را مانند «GitHub نیز جیکوئری را کنار گذاشت» زیاد میشنوید. نه فقط کنار گذاشتن jQuery یک وابستگی ثالث را از برنامه حذف میکند، بلکه کار مستقیم با native API مرورگرها همواره به مراتب سریعتر است از کتابخانههایی که سطح بالایی از abstraction آنها را ارائه میدهند.
یافتن عناصر توسط JavaScript خالص
زمانیکه نیاز به انتخاب عناصری در صفحه باشند، بلافاصله ذهن ما به سمت ('myElement#')$ و ('myElement.')$ جیکوئری، معطوف میشود. اما ... این روزها برای انجام این نوع کارها واقعا نیازی به jQuery نیست!
یافتن عناصر بر اساس ID آنها
<div id="my-element-id"></div>
var result = $('#my-element-id');
انجام این کار توسط web API و یا همان JavaScript خالص، چنین شکلی را دارد:
var result = document.getElementById('my-element-id');
روش دیگر انجام اینکار با JavaScript به صورت زیر است:
var result = document.querySelector('#my-element-id');
در هر دو حالت، خروجی مقایسهی ذیل، true است:
result.id === 'my-element-id'; // returns true
یافتن عناصر بر اساس کلاسهای CSS
<span class="some-class"></span>
var result = $('.some-class');
var result = document.getElementsByClassName('some-class');
var result = document.querySelectorAll('.some-class');
result[0].className === 'some-class'; // returns true
یافتن عناصر بر اساس تگهای عناصر
<code>Console.WriteLine("Hello world!");</code>
var result = $('code');
var result = document.getElementsByTagName('code');
var result = document.querySelectorAll('code');
result[0].tagName === 'code'; // returns true
یافتن عناصر بر اساس کلاس نماها (Pseudo-classes)
Pseudo-classes از زمان ابتداییترین پیشنویس استاندارد CSS وجود داشتهاند. برای مثال visited: یک Pseudo-classes است و به لینکهای بازدید شدهی توسط کاربر اشاره میکند و یا focus: به المانی اشاره میکند که هم اکنون دارای focus است.
<form> <label>Full Name <input name="full-name"> </label> <label>Company <input name="company"> </label> </form>
var focusedInputs = $('INPUT:focus');
var companyInput = document.querySelector('INPUT:focus');
یافتن عناصر بر اساس ارتباط والد و فرزندی آنها
<div> <a href="https://www.dntips.ir"> <span>Go to site</span> </a> <p>Some text</p> Some other text </div>
روش یافتن والد anchor tag در جیکوئری توسط متد parent؛ با فرض اینکه a$ به شیء anchor اشاره میکند:
var $result = $a.parent();
var result = a.parentNode;
یافتن فرزندان:
در جیکوئری:
var result = $('#myParent').children();
var result = document.querySelectorAll('DIV > *');
var result = document.querySelectorAll('DIV > P');
var result = document.getElementById('myParent').childNodes; var result = div.childNodes;
var result =document.getElementById('myParent').children;
var result =document.querySelectorAll('#myParent A');
جستجوی عناصر با صرفنظر کردن از بعضی از آنها
<ul role="menu"> <li>choice 1</li> <li class="active">choice 2</li> <li>choice 3</li> </ul>
var $result = $('UL LI').not('.active');
var result = document.querySelectorAll('UL LI:not(.active)');
انتخاب چندین المان با هم
<div id="link-container"> <a href="https://github.com/VahidN">GitHub</a> </div> <ol> <li>one</li> <li>two</li> </ol> <span class="my-name">VahidN</span>
var $result = $('#link-container, .my-name, OL');
var result = document.querySelectorAll('#link-container, .my-name, OL');
یافتن گروهی از المانها بر اساس نوع آنها:
var result = document.querySelectorAll( 'BUTTON[type="submit"], INPUT[type="submit"]' );
جایگزین کردن $ جیکوئری با جاوا اسکریپت
تا اینجا حتما به شباهت کدهای خالص جاوا اسکریپت و jQuery دقت کردهاید. اگر بخواهیم برای $ جیکوئری، یک معادل جاوا اسکریپتی تهیه کنیم، به قطعه کد زیر خواهیم رسید:
window.$ = function(selector) { return document.querySelectorAll(selector); };
$('.some-class'); $('#some-id'); $('.some-parent > .some-child'); $('UL LI:not(.active)');
با سلام.
من از این کپچا در یک سرور استفاده کردم، بر روی پورت 80 هیچ خطایی نمیگیرم و تصویر امنیتی بدرستی نمایش داده میشود ولی روی پورت 8080 تصویر کپچا لود نمیشود و خطای زیر را در کنسول کروم لاگ میکند:
با تشکر.
من از این کپچا در یک سرور استفاده کردم، بر روی پورت 80 هیچ خطایی نمیگیرم و تصویر امنیتی بدرستی نمایش داده میشود ولی روی پورت 8080 تصویر کپچا لود نمیشود و خطای زیر را در کنسول کروم لاگ میکند:
System.Security.Cryptography.CryptographicException: Object already exists
Resource interpreted as Image but transferred with MIME type text/html: "http://site:8080/Error/Index?aspxerrorpath=/Shared/CaptchaImage"
, What Every JavaScript Developer Should Know About ECMAScript 2015 is the book I'd like to read about the new features in the JavaScript language. The book isn't a reference manual or an exhaustive list of everything in the ES2015 specification. Instead, I purposefully selected what I think are the important features we will use in everyday programming.
مطالب دورهها
شی گرایی در #F
برنامه نویسی شی گرای سومین نسل از الگوهای اصلی برنامه نویسی است. در توضیحات فصل اول گفته شد که #F یک زبان تابع گرا است ولی این بدان معنی نیست که #F از مفاهیمی نظیر کلاس و یا interface پشتیبانی نکند. برعکس در #F امکان تعریف کلاس و interface و هم چنین پیاده سازی مفاهیم شی گرایی وجود دارد.
*با توجه به این موضوع که فرض است دوستان با مفاهیم شی گرایی آشنایی دارند از توضیح و تشریح این مفاهیم خودداری میکنم.
Classes
کلاس چارچوبی از اشیا است برای نگهداری خواص(Properties) و رفتار ها(Methods) و رخدادها(Events). کلاس پایه ایترین مفهوم در برنامه نویسی شی گراست. ساختار کلی تعربف کلاس در #F به صورت زیر است:
همان طور که در ساختار بالا میبینید مفاهیم access-modifier و inherit و constructor هم در #F وجود دارد.
انواع access-modifier در #F
کلاس بالا دارای یک سازنده است که دو پارامتر ورودی میگیرد. کلمه end به معنای انتهای کلاس است. برای استفاده کلاس باید به صورت زیر عمل کنید:
توابع و خواص در کلاس ها
برای تعریف خاصیت در #F باید از کلمه کلیدی member استفاده کنید. در مثال بعدی برای کلاس بالا تابع و خاصیت تعریف خواهیم کرد.
کلاس بالا دارای سه خاصیت به نامهای Number و Name و Amount است و دو تابع به نامهای Deposit و Withdraw دارد. اما x استفاده شده قبل از هر member به معنی this در #C است. در #F شما برای اشاره به شناسههای یک محدوده خودتون باید یک نام رو برای اشاره گر مربوطه تعیین کنید.
استفاده از کلمه do
در #F زمانی که قصد داشته باشیم در بعد از وهله سازی از کلاس و فراخوانی سازنده، عملیات خاصی انجام شود(مثل انجام برخی عملیات متداول در سازندههای کلاسهای دات نت) باید از کلمه کلیدی do به همراه یک بلاک از کد استفاده کنیم.
یک مثال در این زمینه:
در مثال بالا دو عبارت do یکی به صورت static و دیگری به صورت غیر static تعریف شده اند. استفاده از do به صورت غیر static این امکان را به ما میدهد که بتوانیم به تمام شناسهها و توابع تعریف شده در کلاس استفاده کنیم ولی do به صورت static فقط به خواص و توابع از نوع static در کلاس دسترسی دارد.
خروجی مثال بالا:
خواص static:
برای تعریف خواص به صورت استاتیک مانند #C از کلمه کلیدی static استفاده کنید.مثالی در این زمینه:
SomeStaticMethod به صورت استاتیک تعریف شده در حالی که x.Prop به صورت غیر استاتیک. دسترسی به متدها یا خواص static باید بدون وهله سازی از کلاس انجام بگیرد در غیر این صورت با خطای کامپایلر روبرو خواهید شد.
روش استفاده درست:
متدهای get , set در خاصیت ها:
همانند #C و سایر زبانهای دات نت امکان تعریف متدهای get و set برای خاصیتهای یک کلاس وجود دارد.
ساختار کلی:
مثالی در این زمینه:
کد متناظر در #C:
یا به صورت:
Interface ها
اینترفیس به تمامی خواص و توابع عمومی اشئایی که آن را پیاده سازی کرده اند اشاره میکند. (توضیحات بیشتر (^ ) و (^ ))ساختار کلی برای تعریف آن به صورت زیر است:
مثال:
استفاده از حرف I برای شروع نام اینترفیس طبق قوانین تعریف شده (اختیاری) برای نام گذاری است.
نکته: در هنگام تعریف توابع و خاصیت در interfaceها باید از کلمه abstract استفاده کنیم. هر کلاسی که از یک یا چند تا اینترفیس ارث ببرد باید تمام خواص و توابع اینتریسها را پیاده سازی کند. در مثال بعدی کلاس SomeClass1 اینترفیس بالا را پیاده سازی میکند. دقت کنید که کلمه this توسط من به عنوان اشاره گر به اشیای کلاس تعیین شده و شما میتونید از هر کلمه یا حرف دیگری استفاده کنید.
نکته مهم: اگر قصد فراخوانی متد Print را در کلاس بالا دارید نمیتونید به صورت مستقیم متد بالا را فراخوانی کنید. بلکه حتما باید کلاس به
اینترفیس مربوطه cast شود.
روش نادرست:
روش درست:
برای عملیات cast ازاستفاده کنید.
در مثال بعدی کلاسی خواهیم داشت که از سه اینترفیس ارث میبرد. در نتیجه باید تمام متدهای هر سه اینترفیس را پیاده سازی کند.
فراخوانی این متدها نیز به صورت زیر خواهد بود:
کلاسهای Abstract
#F از کلاسهای abstract هم پشتیبانی میکند. اگر با کلاسهای abstract در #C آشنایی ندارید میتونید مطالب مورد نظر رو در (^ ) و (^ ) مطالعه کنید. به صورت خلاصه کلاسهای abstract به عنوان کلاسهای پایه در برنامه نویسی شی گرا استفاده میشوند. این کلاسها دارای خواص و متدهای پیاده سازی شده و نشده هستند. خواص و متد هایی که در کلاس پایه abstract پیاده سازی نشده اند باید توسط کلاس هایی که از این کلاس پایه ارث میبرند حتما پیاده سازی شوند.
ساختار کلی تعریف کلاسهای abstract:
در #F برای این که مشخص کنیم که یک کلاس abstract است حتما باید [<AbstractClass>] در بالای کلاس تعریف شود.
کلاس بالا تعریفی از کلاس abstract است که سه خصوصیت abstract دارد (برای
تعیین خصوصیتها و متد هایی که در کلاس پایه پیاده سازی نمیشوند از کلمه
کلیدی abstract در هنگام تعریف آنها استفاده میکنیم). حال دو کلاس ایجاد میکنیم که این کلاس پایه را پیاده سازی کنند.
#1 کلاس اول
#2 کلاس دوم
Structures
structureها در #F دقیقا معال struct در #C هستند. توضیحات بیشتر درباره struct در #C (^ ) و (^ )). اما به طور خلاصه باید ذکر کنم که strucureها تقریبا دارای مفهوم کلاس هستند با اندکی تفاوت که شامل موارد زیر است:
یک نکته مهم هنگام کار با structها در #F این است که امکان استفاده از let
و Binding در structها وجود ندارد. به جای آن باید از val استفاده کنید.
تفاوت اصلی بین val و let در این است که هنگام تعریف
شناسه با val امکان مقدار دهی اولیه به شناسه وجود ندارد. در مثال بالا
مقادیر برای x و y و z برابر 0.0 است که توسط کامپایلر انجام میشود. در
ادامه یک struct به همراه سازنده تعریف میکنیم:
توسط سازنده struct بالا مقادیر اولیه x و y دریافت میشود به متغیرهای متناظر انتساب میشود.
در پایان یک مثال مشترک رو در #C و #F پیاده سازی میکنیم:
*با توجه به این موضوع که فرض است دوستان با مفاهیم شی گرایی آشنایی دارند از توضیح و تشریح این مفاهیم خودداری میکنم.
Classes
کلاس چارچوبی از اشیا است برای نگهداری خواص(Properties) و رفتار ها(Methods) و رخدادها(Events). کلاس پایه ایترین مفهوم در برنامه نویسی شی گراست. ساختار کلی تعربف کلاس در #F به صورت زیر است:
type [access-modifier] type-name [type-params] [access-modifier] ( parameter-list ) [ as identifier ] = [ class ] [ inherit base-type-name(base-constructor-args) ] [ let-bindings ] [ do-bindings ] member-list ... [ end ] type [access-modifier] type-name1 ... and [access-modifier] type-name2 ... ...
انواع access-modifier در #F
- public : دسترسی برای تمام فراخوانها امکان پذیر است
- internal : دسترسی برای تمام فراخوان هایی که در همین assembly هستند امکان پذیر است
- private : دسترسی فقط برای فراخوانهای موجود در همین ماژول امکان پذیر است
نکته : protected access modifier در #F پشتیبانی نمیشود.
مثالی از تعریف کلاس:
type Account(number : int, name : string) = class let mutable amount = 0m end
let myAccount = new Account(123456, "Masoud")
برای تعریف خاصیت در #F باید از کلمه کلیدی member استفاده کنید. در مثال بعدی برای کلاس بالا تابع و خاصیت تعریف خواهیم کرد.
type Account(number : int, name: string) = class let mutable amount = 0m member x.Number = number member x.Name= name member x.Amount = amount member x.Deposit(value) = amount <- amount + value member x.Withdraw(value) = amount <- amount - value end
open System type Account(number : int, name: string) = class let mutable amount = 0m member x.Number = number member x.Name= name member x.Amount = amount member x.Deposit(value) = amount <- amount + value member x.Withdraw(value) = amount <- amount - value end
let masoud= new Account(12345, "Masoud") let saeed = new Account(67890, "Saeed") let transfer amount (source : Account) (target : Account) = source.Withdraw amount target.Deposit amount let printAccount (x : Account) = printfn "x.Number: %i, x.Name: %s, x.Amount: %M" x.Number x.Name x.Amount let main() = let printAccounts() = [masoud; saeed] |> Seq.iter printAccount printfn "\nInializing account" homer.Deposit 50M marge.Deposit 100M printAccounts() printfn "\nTransferring $30 from Masoud to Saeed" transfer 30M masoud saeed
printAccounts() printfn "\nTransferring $75 from Saeed to Masoud" transfer 75M saeed masoud printAccounts() main()
در #F زمانی که قصد داشته باشیم در بعد از وهله سازی از کلاس و فراخوانی سازنده، عملیات خاصی انجام شود(مثل انجام برخی عملیات متداول در سازندههای کلاسهای دات نت) باید از کلمه کلیدی do به همراه یک بلاک از کد استفاده کنیم.
open System open System.Net type Stock(symbol : string) = class let mutable _symbol = String.Empty do //کد مورد نظر در این جا نوشته میشود end
open System type MyType(a:int, b:int) as this = inherit Object() let x = 2*a let y = 2*b do printfn "Initializing object %d %d %d %d %d %d" a b x y (this.Prop1) (this.Prop2) static do printfn "Initializing MyType." member this.Prop1 = 4*x member this.Prop2 = 4*y override this.ToString() = System.String.Format("{0} {1}", this.Prop1, this.Prop2) let obj1 = new MyType(1, 2)
خروجی مثال بالا:
Initializing MyType. Initializing object 1 2 2 4 8 16
برای تعریف خواص به صورت استاتیک مانند #C از کلمه کلیدی static استفاده کنید.مثالی در این زمینه:
type SomeClass(prop : int) = class member x.Prop = prop static member SomeStaticMethod = "This is a static method" end
let instance = new SomeClass(5);; instance.SomeStaticMethod;; output: stdin(81,1): error FS0191: property 'SomeStaticMethod' is static.
SomeClass.SomeStaticMethod;; (* invoking static method *)
همانند #C و سایر زبانهای دات نت امکان تعریف متدهای get و set برای خاصیتهای یک کلاس وجود دارد.
ساختار کلی:
member alias.PropertyName with get() = some-value and set(value) = some-assignment
type MyClass() = class let mutable num = 0 member x.Num with get() = num and set(value) = num <- value end;;
public int Num { get{return num;} set{num=value;} }
type MyClass() = class let mutable num = 0 member x.Num with get() = num and set(value) = if value > 10 || value < 0 then raise (new Exception("Values must be between 0 and 10")) else num <- value end
Interface ها
اینترفیس به تمامی خواص و توابع عمومی اشئایی که آن را پیاده سازی کرده اند اشاره میکند. (توضیحات بیشتر (^ ) و (^ ))ساختار کلی برای تعریف آن به صورت زیر است:
type type-name = interface inherits-decl member-defns end
type IPrintable = abstract member Print : unit -> unit
نکته: در هنگام تعریف توابع و خاصیت در interfaceها باید از کلمه abstract استفاده کنیم. هر کلاسی که از یک یا چند تا اینترفیس ارث ببرد باید تمام خواص و توابع اینتریسها را پیاده سازی کند. در مثال بعدی کلاس SomeClass1 اینترفیس بالا را پیاده سازی میکند. دقت کنید که کلمه this توسط من به عنوان اشاره گر به اشیای کلاس تعیین شده و شما میتونید از هر کلمه یا حرف دیگری استفاده کنید.
type SomeClass1(x: int, y: float) = interface IPrintable with member this.Print() = printfn "%d %f" x y
روش نادرست:
let instance = new SomeClass1(10,20) instance.Print//فراخوانی این متد باعث ایجاد خطای کامپایلری میشود.
let instance = new SomeClass1(10,20) let instanceCast = instance :> IPrintable// استفاده از (<:) برای عملیات تبدیل کلاس به اینترفیس instanceCast.Print
در مثال بعدی کلاسی خواهیم داشت که از سه اینترفیس ارث میبرد. در نتیجه باید تمام متدهای هر سه اینترفیس را پیاده سازی کند.
type Interface1 = abstract member Method1 : int -> int type Interface2 = abstract member Method2 : int -> int type Interface3 = inherit Interface1 inherit Interface2 abstract member Method3 : int -> int type MyClass() = interface Interface3 with member this.Method1(n) = 2 * n member this.Method2(n) = n + 100 member this.Method3(n) = n / 10
let instance = new MyClass() let instanceToCast = instance :> Interface3 instanceToCast.Method3 10
#F از کلاسهای abstract هم پشتیبانی میکند. اگر با کلاسهای abstract در #C آشنایی ندارید میتونید مطالب مورد نظر رو در (^ ) و (^ ) مطالعه کنید. به صورت خلاصه کلاسهای abstract به عنوان کلاسهای پایه در برنامه نویسی شی گرا استفاده میشوند. این کلاسها دارای خواص و متدهای پیاده سازی شده و نشده هستند. خواص و متد هایی که در کلاس پایه abstract پیاده سازی نشده اند باید توسط کلاس هایی که از این کلاس پایه ارث میبرند حتما پیاده سازی شوند.
ساختار کلی تعریف کلاسهای abstract:
[<AbstractClass>] type [ accessibility-modifier ] abstract-class-name = [ inherit base-class-or-interface-name ] [ abstract-member-declarations-and-member-definitions ] abstract member member-name : type-signature
[<AbstractClass>] type Shape(x0 : float, y0 : float) = let mutable x, y = x0, y0 let mutable rotAngle = 0.0 abstract Area : float with get abstract Perimeter : float with get abstract Name : string with get
#1 کلاس اول
type Square(x, y,SideLength) = inherit Shape(x, y)
override this.Area = this.SideLength * this.SideLength override this.Perimeter = this.SideLength * 4. override this.Name = "Square"
type Circle(x, y, radius) = inherit Shape(x, y)
let PI = 3.141592654 member this.Radius = radius override this.Area = PI * this.Radius * this.Radius override this.Perimeter = 2. * PI * this.Radius
structureها در #F دقیقا معال struct در #C هستند. توضیحات بیشتر درباره struct در #C (^ ) و (^ )). اما به طور خلاصه باید ذکر کنم که strucureها تقریبا دارای مفهوم کلاس هستند با اندکی تفاوت که شامل موارد زیر است:
- structureها از نوع مقداری هستند و این بدین معنی است مستقیما درون پشته ذخیره میشوند.
- ارجاع به structureها از نوع ارجاع با مقدار است بر خلاف کلاسها که از نوع ارجاع به منبع هستند.(^ )
- structureها دارای خواص ارث بری نیستند.
- عموما از structure برای ذخیره مجموعه ای از دادهها با حجم و اندازه کم استفاده میشود.
ساختار کلی تعریف structure
[ attributes ] type [accessibility-modifier] type-name = struct type-definition-elements end //یا به صورت زیر [ attributes ] [<StructAttribute>] type [accessibility-modifier] type-name = type-definition-elements
type Point3D = struct val x: float val y: float val z: float end
type Point2D = struct val X: float val Y: float new(x: float, y: float) = { X = x; Y = y } end
در پایان یک مثال مشترک رو در #C و #F پیاده سازی میکنیم:
نظرات مطالب
تاریخ شمسی با Extension Method برای DateTime
سلام خیلی خوبه منم از کلاس زیر استفاده میکنم
اما تو سیلورلایت چطور استفاده کنم؟
اما تو سیلورلایت چطور استفاده کنم؟
using System; using System.Collections.Generic; using System.Globalization; using System.Reflection; using System.Text; public class PersianCulture : CultureInfo { private readonly Calendar cal; private readonly Calendar[] optionals; public PersianCulture(): this("FA-IR", true) { } public PersianCulture(string cultureName, bool useUserOverride): base(cultureName, useUserOverride) { //Temporary Value for cal. cal = base.OptionalCalendars[0]; //populating new list of optional calendars. var optionalCalendars = new List<Calendar>(); optionalCalendars.AddRange(base.OptionalCalendars); optionalCalendars.Insert(0, new PersianCalendar()); Type formatType = typeof(DateTimeFormatInfo); Type calendarType = typeof(Calendar); PropertyInfo idProperty = calendarType.GetProperty("ID", BindingFlags.Instance | BindingFlags.NonPublic); FieldInfo optionalCalendarfield = formatType.GetField("optionalCalendars", BindingFlags.Instance | BindingFlags.NonPublic); //populating new list of optional calendar ids var newOptionalCalendarIDs = new Int32[optionalCalendars.Count]; for (int i = 0; i < newOptionalCalendarIDs.Length; i++) newOptionalCalendarIDs[i] = (Int32)idProperty.GetValue(optionalCalendars[i], null); optionalCalendarfield.SetValue(DateTimeFormat, newOptionalCalendarIDs); optionals = optionalCalendars.ToArray(); cal = optionals[0]; DateTimeFormat.Calendar = optionals[0]; DateTimeFormat.MonthNames = new[] { "فروردین", "اردیبهشت", "خرداد", "تیر", "مرداد", "شهریور", "مهر", "آبان", "آذر", "دی", "بهمن", "اسفند", string.Empty }; DateTimeFormat.MonthGenitiveNames = new[] { "فروردین", "اردیبهشت", "خرداد", "تیر", "مرداد", "شهریور", "مهر", "آبان", "آذر", "دی", "بهمن", "اسفند", string.Empty }; DateTimeFormat.AbbreviatedMonthNames = new[] { "فروردین", "اردیبهشت", "خرداد", "تیر", "مرداد", "شهریور", "مهر", "آبان", "آذر", "دی", "بهمن", "اسفند", string.Empty }; DateTimeFormat.AbbreviatedMonthGenitiveNames = new[] { "فروردین", "اردیبهشت", "خرداد", "تیر", "مرداد", "شهریور", "مهر", "آبان", "آذر", "دی", "بهمن", "اسفند", string.Empty }; DateTimeFormat.AbbreviatedDayNames = new string[] { "ی", "د", "س", "چ", "پ", "ج", "ش" }; DateTimeFormat.ShortestDayNames = new string[] { "ی", "د", "س", "چ", "پ", "ج", "ش" }; DateTimeFormat.DayNames = new string[] { "یکشنبه", "دوشنبه", "ﺳﻪشنبه", "چهارشنبه", "پنجشنبه", "جمعه", "شنبه" }; DateTimeFormat.AMDesignator = "ق.ظ"; DateTimeFormat.PMDesignator = "ب.ظ"; DateTimeFormat.ShortDatePattern = "yyyy/MM/dd"; DateTimeFormat.LongDatePattern = "yyyy/MM/dd"; DateTimeFormat.SetAllDateTimePatterns(new[] {"yyyy/MM/dd"}, 'd'); //DateTimeFormat.SetAllDateTimePatterns(new[] {"dddd, dd MMMM yyyy"}, 'D'); //DateTimeFormat.SetAllDateTimePatterns(new[] {"yyyy MMMM"}, 'y'); //DateTimeFormat.SetAllDateTimePatterns(new[] {"yyyy MMMM"}, 'Y'); } public override Calendar Calendar { get { return cal; } } public override Calendar[] OptionalCalendars { get { return optionals; } } }