نظرات مطالب
مثال اگر نیاز دارید سری بحثهای معماری لایه بندی نرم افزار در سایت مفید است. همچنین اگر تمام متدها رو داخل یک فرم قرار دادید بهتره از مطلب آشنایی با Refactoring - قسمت 1 شروع کنید.
در این قسمت هدف آشنایی اولیه با کوئری نویسی و اجزای آن بود. در اینجا کارایی و سرعت عمل کوئریها مطرح نیست. البته این بسته به نیاز و جایگاه آن کوئری مورد نظر بر میگردد، خیلی جاها به categoryهای productها احتیاجی نیست.
نظرات مطالب
آشنایی با NHibernate - قسمت هشتم
دوست عزیز. سری آموزش NH واقعا یک کار نمونه و عالی هست که انجام دادید. خسته نباشید و ممنون.
راستی گویا پیدا کردن شما روی سایتهای Social مثل Twitter یا Facebook و ... کار ساده ای نیست! به هر حال از آشنایی بیشتر با شما خوشوقت خواهم شد.
راستی گویا پیدا کردن شما روی سایتهای Social مثل Twitter یا Facebook و ... کار ساده ای نیست! به هر حال از آشنایی بیشتر با شما خوشوقت خواهم شد.
بازخوردهای پروژهها
فیلم آموزشی mvc
با سلام؛ اگه میشد پس از اتمام هر مبحث آموزشی مثلا EF یا MVC و یا ... یک پروژه واقعی در قالب فیلم آموزشی آموزش داده میشد تاثیر خیلی زیادی تو یادگیری افراد و همچنین آشنایی با نحوه کد نویسی افراد حرفهای داشت.
نظرات نظرسنجیها
آیا قصد مهاجرت به NET Core. را دارید؟
برای پروژههای آینده قطعا این کار باید انجام شود، ولی در حال حاظر .net core خیلی جوان است و بسیاری امکانات و همچنین ابزارهای جانبی آن برای پروژههای بزرگ و سازمانی آماده نیست. آشنایی و تست امکانات آن راهگشاست ولی حداقل از ابتدای سال 2017 باید به استفاده از آن فکر کرد.
اشتراکها
سایت «خاتمه یافته توسط مایکروسافت»
اشتراکها
بکارگیری Microsoft Report در Mvc
اشتراکها
معرفی Xamarin.Forms 4.0
Xamarin.Forms 4.0 is here with an array of new features and capabilities to help you create amazing, multi-experience apps that perform efficiently and look great. Xamarin.Forms 4.0.0 also introduces Shell—a simplified, navigation-aware container that makes it easier to build mobile applications. The upgrade path to 4.0 and Shell is also designed to be as simple as possible. Finally, the latest release adds extended support for faster renderers and a unified image source feature to help you create better apps for your users.
هر برنامهی وبی، نیاز به کار با فرمهای وب را دارد و به همین جهت، 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 بودن این اطلاعات نخواهد شد.
نظرات اشتراکها
ماشین حساب ویندوز سورس باز شد!
جالب اینکه حتی ماشین حساب هم آمار استفاده با جزئیات زیاد میفرسته: