Head.InnerHtml = SEO.GenerateMetaTag(....)
استفاده از defer Attribute
<script src="file.js" defer></script>
استفاده از async Attribute
<script src="file.js" async></script>
تصویر فوق یک سناریوی سادهی بارگذاری صفحهی html را به همراه یک فایل جاوااسکریپت با تگ script ساده را نمایش میدهد. همانطور که ملاحظه میکنید، در لحظهای که فایل اسکریپت شروع به دانلود شدن میکند (قسمت بنفش)، عملیات parse صفحه متوقف شده تا زمانی که فایل دانلود و اجرا شود (قسمت قرمز). پس از آن دوباره عملیات parsing از سر گرفته میشود. این عملیات به همراه تگ script ساده صورت گرفته است. حال در تصویر بعد قصد داریم که همین سناریو را به کمک defer Attribute بررسی کنیم:
همانطور که ملاحظه میکنید به کمک defer، شروع عملیات دانلود فایل اسکریپت، خللی در عملیات html parse ایجاد نمیکند و این دو عملیات به صورت موازی میتوانند اجرا شوند. پس از دریافت فایل اسکریپت، این فایل مستقیما اجرا نمیشود و لزوما باید پس از عملیات parsing صورت پذیرد.
و اما تصویر زیر، سناریوی فوق را به همراه async attribute نمایش میدهد:
در این حالت عملیات دریافت فایل اسکریپت همانند defer به صورت موازی اجرا میشود. اما تفاوتی که در این قسمت و در async وجود دارد، زمان اجرا شدن فایلهای اسکریپت است. همانطور که ملاحظه میکنید دقیقا پس از دریافت فایل، اسکریپت ما در اجراست و پس از اجرای کامل، دوباره عملیات parsing ادامه مییابد. در حقیقت اسکریپتهای با async attribute با فراخوانی رخداد onload شروع به اجرا میکنند. جهت استفادهی از async، خیالتان راحت باشد؛ با توجه به این منبع، تقریبا تمام مرورگرها از این ویژگی پشتیبانی میکنند.
مقایسه فریمورکهای UI
داخل لینک آخر هر قسمت دو تب scss و sass دارد و تفاوت در وجود { } در کد css بود. میشه یه توضیحی بدهید.
تشکر
this.modalInit('Insert Code', this.opts.path + '/plugins/paste_code.html', 600, $.proxy(function () {
this.opts.path
و متاسفانه از این مرحله بیشتر نتونستم پیش برم ...
برش تصاویر قبل از آپلود (Crop)
<div style="overflow: auto"> <img id="preview" /> </div>
در این بین من با استفاده از فریمورک بوت استراپ کلاس img-responsive را به تگ image میدهم تا تصویر در هر صفحه نمایشی متناسب با اندازه آن نمایش داده شود. مشکل زیبایی تصویر در صفحه و نحوه کراپ کردن آن حل میشود ولی مشکل جدیدتر این است که تصویری که کراپ میشود آن ناحیه ای نیست که شما قبلا انتخاب کرده بودید؛ دلیل آن هم این است که تصویری که responsive شده است اندازه جدیدی برای خود دارد و برش ناحیه در سمت کلاینت و مختصاتی که به شما داده میشود بر اساس اندازه تغییر یافته است ولی در سمت سرور شما با اندازه واقعی تصویر سر و کار دارید و به همین دلیل مختصات ناحیه برش داده شده اشتباه بوده و قسمتهای دیگری از تصویر برش میخورند.
برای حل این مشکل ابتدا دو المان مخفی زیر را به صفحه اضافه میکنیم:
<input type="hidden" id="RealW" name="RealW" /> <input type="hidden" id="RealH" name="RealH" />
سپس کدهای زیر را به فایل js به شکل زیر ویرایش میکنیم:
$('#preview').Jcrop({ aspectRatio: 2, bgFade: true, bgOpacity: .3, onChange: updateInfo, onSelect: updateInfo }, function () { // use the Jcrop API to get the real image size //============== خطوط جدید $("#RealW").val($('#preview').css("width").replace("px", "")); $("#RealH").val($('#preview').css("height").replace("px", "")); //============== jcrop_api = this; });
سپس در سمت کلاینت با محاسبه فرمولهای تناسب این مشکل را رفع میکنیم تا مختصاتهای دریافت شده را به مختصاتهای واقعی تبدیل کنیم:
public static byte[] Resize(this byte[] byteImageIn, int x1,int y1,int x2,int y2,int realW,int realH) { //convert to full size image ImageConverter ic = new ImageConverter(); Image src = (Image)(ic.ConvertFrom(byteImageIn)); //original size if (src == null) return null; // چهار خط زیر فرمول تناسب را اجرا میکند x1 = src.Width * x1 / realW; x2 = src.Width * x2 / realW; y1 = src.Height * y1 / realH; y2 = src.Height * y2 / realH; Bitmap target = new Bitmap(x2 - x1, y2 - y1); using (Graphics graphics = Graphics.FromImage(target)) graphics.DrawImage(src, new Rectangle(0, 0, target.Width, target.Height), new Rectangle(x1,y1,x2-x1,y2-y1), GraphicsUnit.Pixel); src = target; using (var ms = new MemoryStream()) { src.Save(ms, System.Drawing.Imaging.ImageFormat.Jpeg); return ms.ToArray(); } }
namespace Microsoft.Extensions.DependencyInjection { public interface IServiceCollection : ICollection<ServiceDescriptor>, IEnumerable<ServiceDescriptor>, IEnumerable, IList<ServiceDescriptor> { } }
ServiceProvider و مؤلفههای درونی آن، از یک مجموعه از ServiceDescriptorها برای برنامهی شما بر اساس سرویسهای ثبت شدهی توسط IServiceCollection استفاده میکنند. ServiceDescriptor حاوی اطلاعاتی در مورد سرویسهای ثبت شدهاست. اگر به کد منبع این کلاس برویم، میبینیم پنج Property اصلی دارد که با استفاده از آنها اطلاعات یک سرویس ثبت و نگهداری میشوند. با استفاده از این اطلاعات در هنگام اجرا ، DI Container به واکشی و ساخت نمونههایی از سرویس درخواستی اقدام میکند:
public Type ImplementationType { get; } public object ImplementationInstance { get; } public Func<IServiceProvider, object> ImplementationFactory { get; } public ServiceLifetime Lifetime { get; } public Type ServiceType { get; }
هر کدام از این Property ها کاربرد خاص خود را دارند:
- · ServiceType : نوع سرویسی را که میخواهیم ثبت شود، مشخص میکنیم ( مثلا اینترفیس IMessageService ) .
- · ImplementionType : نوع پیاده سازی سرویس مورد نظرمان را مشخص میکند ( مثلا کلاس MessageService ).
- · LifeTime : طول حیات سرویس را مشخص میکند. DI Container بر اساس این ویژگی، اقدام به ساخت و از بین بردن نمونههایی از سرویس میکند.
- · ImplementionInstance : نمونهی ساخته شدهی از سرویس است.
- · ImplementionFactory : یک Delegate است که چگونگی ساخته شدن یک نمونه از پیاده سازی سرویس را در خود نگه میدارد. این Delegate یک IServiceProvider را به عنوان ورودی دریافت میکند و یک object را بازگشت میدهد.
به صورت عادی، در سناریوهای معمول ثبت سرویسها درون IServiceCollection، نیازی به استفاده از ServiceDescriptor نیست؛ ولی اگر بخواهیم سرویسها را به روشهای پیشرفتهتری ثبت کنیم، مجبوریم که به صورت مستقیم با این کلاس کار کنیم.
می توانیم یک ServiceDesciriptor را به روشهای زیر تعریف کنیم:
var serviceDescriptor1 = new ServiceDescriptor( typeof(IMessageServiceB), typeof(MessageServiceBB), ServiceLifetime.Scoped); var serviceDescriptor2 = ServiceDescriptor.Describe( typeof(IMessageServiceB), typeof(MessageServiceBB), ServiceLifetime.Scoped); var serviceDescriptor3 = ServiceDescriptor.Singleton(typeof(IMessageServiceB), typeof(MessageServiceBB)); var serviceDescriptor4 = ServiceDescriptor.Singleton<IMessageServiceB, MessageServiceBB>();
همانطور که دیدیم، IServiceCollection در
واقع لیست و مجموعهای از اشیاء است که از نمونههای جنریک IServiceCollection ، IList ، IEnumerable و Ienumberabl ارث بری میکند؛ بنابراین میتوان از متدهای تعریف شدهی در این
اینترفیسها برای IServiceCollection نیز استفاده کرد. حالا ما برای اضافه کردن این سرویسهای جدید،
بدین طریق عمل میکنیم:
Services.Add(serviceDescriptor1);
استفاده از متدهای TryAdd()
به کد زیر نگاه کنید :
services.AddScoped<IMessageServiceB, MessageServiceBA>(); services.AddScoped<IMessageServiceB, MessageServiceBB>();
برای جلوگیری از این خطا میتوانیم از متدهای TryAddSingleton() ، TryAddScoped() و TryAddTransient() استفاده کنیم. این متدها درون فضای نام Microsoft.Extionsion.DependencyInjection.Extension قرار دارند.
عملکرد کلی این
متدها درست مثل متدهای Add() است؛ با این تفاوت که این متد ابتدا IServiceCollection را جستجو میکند و اگر برای type مورد نظر سرویسی ثبت نشده بود،
آن را ثبت میکند:
services.TryAddScoped<IMessageServiceB, MessageServiceBA>(); services.TryAddScoped<IMessageServiceB, MessageServiceBB>();
جایگذاری یک سرویس با نمونهای دیگر
گاهی اوقات میخواهیم یک پیاده سازی دیگر را بجای پیاده سازی فعلی، در DI Container ثبت کنیم. در این حالت از متد Replace() بر روی IServiceCollection برای این کار استفاده میکنیم. این متد فقط یک ServiceDescriptor را به عنوان پارامتر ورودی میگیرد:
services.Replace(serviceDescriptor3);
services.RemoveAll<IMessageServiceB>();
معمولا در پروژههای معمول خودمان نیازی به استفاده از Replace() و RemoveAll() نداریم؛ مگر اینکه بخواهیم پیاده سازی اختصاصی خودمان را برای سرویسهای درونی فریم ورک یا کتابخانههای شخص ثالث، بجای پیاده سازی پیش فرض، ثبت و استفاده کنیم.
AddEnumerable()
فرض کنید دارید برنامهی نوبت دهی یک کلینیک را مینویسید و به صورت پیش فرض از شما خواستهاند که هنگام صدور نوبت، این قوانین را بررسی کنید:
- هر شخص در هفته نتواند بیش از 2 نوبت برای یک تخصص بگیرد.
- اگر شخص در ماه بیش از 3 نوبت رزرو شده داشته باشد ولی مراجعه نکرده باشد، تا پایان ماه، امکان رزرو نوبت را نداشته باشد .
- تعداد نوبتهای ثبت شدهی برای پزشک در آن روز نباید بیش از تعدادی باشد که پزشک پذیرش میکند.
- و ...
یک روش معمول برای پیاده سازی این قابلیت، ساخت سرویسی برای ثبت نوبت است که درون آن متدی برای بررسی کردن قوانین ثبت نام وجود دارد. خب، ما این کار را انجام میدهیم. تستهای واحد و تستهای جامع را هم مینویسیم و بعد برنامه را انتشار میدهیم و همه چیز خوب است؛ تا اینکه مالک محصول یک نیازمندی جدید را میخواهد که در آن ما باید قانون زیر را در هنگام ثبت نوبت بررسی کنیم:
- نوبتهای ثبت شده برای یک شخص نباید دارای تداخل باشند.
در این حالت ما باید دوباره سرویس Register را باز کنیم و به متد بررسی کردن قوانین برویم و دوباره کدهایی را برای بررسی کردن قانون جدید بنویسیم و احتمالا کد ما به این صورت خواهد شد:
public class RegisterAppointmentService : RegisterAppointmentService { public Task<Result> RegisterAsync( PatientInfoDTO patientIfno , DateTimeOffset requestedDateTime , PhysicianId phusicianId ) { CheckRegisterantionRule(patientInfo); // code here } private Task CheckRegisterationRule(PatientInfoDTO patientInfo) { CheckRule1(patientInfo); CheckRule2(patientInfo); CheckRule3(patientInfo); } }
در این حالت باید به ازای هر قانون جدید، به متد CheckRegisterationRule برویم و به ازای هر قانون، یک متد private جدید را بسازیم. مشکل این روش این است که در این حالت ما مجبوریم با هر کم و زیاد شدن قانون، این کلاس را باز کنیم و آن را تغییر دهیم و با هر تغییر دوباره، تستهای واحد آن را دوباره نویسی کنیم. در یک کلام در کد بالا اصول Separation of Concern و Open/Closed Principle را رعایت نمیشود.
یک راهکار این است که یک
سرویس جداگانه را برای بررسی کردن قوانین بنویسیم و آن را به سرویس ثبت نوبت تزریق کنیم:
public class ICheckRegisterationRuleForAppointmentService : ICheckRegisterationRuleForAppointmentService { public Task CheckRegisterantionRule(PatientInfoDTO patientInfo) { CheckRule1(patientInfo); CheckRule2(patientInfo); CheckRule3(patientInfo); } } public class RegisterAppointmentService : IRegisterAppointmentService { private ICheckRegisterationRuleForAppointmentService _ruleChecker; public RegisterAppointmentService (RegisterAppointmentService ruleChecker) { _ruleChecker = ruleChecker; } public Task<Result> RegisterAsync( PatientInfoDTO patientIfno , DateTimeOffset requestedDateTime , PhysicianId phusicianId ) { _ruleChecker.CheckRegisterantionRule(patientInfo); // code here } }
با این کار وظیفهی چک کردن قوانین و وظیفهی ثبت و ذخیره سازی قوانین را از یکدیگر جدا کردیم؛ ولی همچنان در سرویس بررسی کردن قوانین، اصل Open/Closed رعایت نشدهاست. خب راه حل چیست !؟
یکی از راه حلهای موجود، استفاده از الگوی قوانین یا Rule Pattern است. برای اجرای این الگو، میتوانیم با تعریف یک اینترفیس کلی برای بررسی کردن قانون، به ازای هر قانون یک پیاده سازی اختصاصی را داشته باشیم:
interface IAppointmentRegisterationRule { Task CheckRule(PatientInfo patientIfno); } public class AppointmentRegisterationRule1 : IAppointmentRegisterationRule { public Task CheckRule(PatientInfo patientIfno) { Console.WriteLine("Rule 1 is checked"); return Task.CompletedTask; } } public class AppointmentRegisterationRule2 : IAppointmentRegisterationRule { public Task CheckRule(PatientInfo patientIfno) {
Console.WriteLine("Rule 2 is checked"); return Task.CompletedTask; } } public class AppointmentRegisterationRule3 : IAppointmentRegisterationRule { public Task CheckRule(PatientInfo patientIfno) { Console.WriteLine("Rule 3 is checked"); return Task.CompletedTask; } } public class AppointmentRegisterationRule4 : IAppointmentRegisterationRule { public Task CheckRule(PatientInfo patientIfno) { Console.WriteLine("Rule 4 is checked"); return Task.CompletedTask; } }
services.AddScoped<IAppointmentRegisterationRule, AppointmentRegisterationRule1>(); services.AddScoped<IAppointmentRegisterationRule, AppointmentRegisterationRule2>(); services.AddScoped<IAppointmentRegisterationRule, AppointmentRegisterationRule3>(); services.AddScoped<IAppointmentRegisterationRule, AppointmentRegisterationRule4>();
public class CheckRegisterationRuleForAppointmentService : ICheckRegisterationRuleForAppointmentService { private IEnumerable<IAppointmentRegisterationRule> _rules ; public CheckRegisterationRuleForAppointmentService(IEnumerable<IAppointmentRegisterationRule> rules) { _rules = rules; } public Task CheckRegisterantionRule(PatientInfoDTO patientInfo) { foreach(var rule in rules) { rule.CheckRule(patientInfo); } } }
کد بالا به
نظر کامل میآید ولی مشکلی دارد! اگر در DI Container برای IAppointmentRegisterationRule یک قانون را دو یا چند بار ثبت کنیم، در هر بار بررسی کردن قوانین، آن را به همان تعداد بررسی میکند و اگر این فرآیند منابع زیادی را به
کار میگیرد، میتواند عملکرد برنامهی ما را به هم بریزد. برای جلوگیری از این مشکل، از متد TryAddEnumerabl()
استفاده میکنیم که لیستی از ServiceDescriptor ها را میگیرد و هر serviceDescriptor را فقط یکبار ثبت میکند:
services.TryAddEnumerable(new[] { ServiceDescriptor.Scoped(typeof(IAppointmentRegisterationRule), typeof(AppointmentRegisterationRule1)), ServiceDescriptor.Scoped(typeof(IAppointmentRegisterationRule), typeof(AppointmentRegisterationRule2)), ServiceDescriptor.Scoped(typeof(IAppointmentRegisterationRule), typeof(AppointmentRegisterationRule3)), ServiceDescriptor.Scoped(typeof(IAppointmentRegisterationRule), typeof(AppointmentRegisterationRule4)), });
AngularJS #1
روشهای مختلف تعریف خاصیت template در یک کامپوننت
در قسمت قبل، روش تعریف inline یک template را مشاهده کردید:
template:` <div><h1>{{pageTitle}}</h1> <div>My First Component</div> </div> `
هر چند این روش تعریف قالبها، مزیت سادگی و امکان مشاهدهی View را به همراه کدهای مرتبط با آن، در یک فایل میسر میکند، اما به دلیل رشتهای بودن، مزیت کار کردن با ادیتورهای وب، مانند داشتن intellisense، فرمت خودکار کدها و بررسی syntax را از دست خواهیم داد و با بیشتر شدن حجم این رشته، این مشکلات بیشتر نمایان خواهند شد.
به همین جهت قابلیت دیگری به نام linked template نیز در اینجا درنظر گرفته شدهاست:
templateUrl: 'product-list.component.html'
ساخت کامپوننت نمایش لیست محصولات
در ادامه میخواهیم کامپوننتی را طراحی کنیم که آرایهای از محصولات را نمایش میدهد. در اینجا مرسوم است هر ویژگی برنامه، در یک پوشهی مجزا قرار گیرد. به همین جهت در ادامهی مثال قسمت قبل که پوشهی app را به ریشهی پروژه اضافه کردیم و سپس main.ts راه انداز و کامپوننت ریشهی سایت یا app.component.ts را در آن تعریف کردیم، در داخل همین پوشهی app، پوشهی جدیدی را به نام products اضافه میکنیم. سپس به این پوشهی جدید محصولات، فایل جدیدی را به نام product-list.component.html اضافه کنید. از این فایل جهت تعریف قالب کامپوننت لیست محصولات استفاده خواهیم کرد. در اینجا نیز مرسوم است نام قالب یک Component را به صورت نام ویژگی ختم شدهی به کلمهی Component، با پسوند html تعریف کنیم.
پس از اضافه شدن فایل product-list.component.html، محتوای آنرا به نحو ذیل تغییر دهید:
<div class='panel panel-default'> <div class='panel-heading'> {{pageTitle}} </div> <div class='panel-body'> <div class='row'> <div class='col-md-2'>Filter by:</div> <div class='col-md-4'> <input type='text' /> </div> </div> <div class='row'> <div class='col-md-6'> <h3>Filtered by: </h3> </div> </div> <div class='table-responsive'> <table class='table'> <thead> <tr> <th> <button class='btn btn-primary'> Show Image </button> </th> <th>Product</th> <th>Code</th> <th>Available</th> <th>Price</th> <th>5 Star Rating</th> </tr> </thead> <tbody> </tbody> </table> </div> </div> </div>
تنها نکتهی AngularJS 2.0 قالب فوق، اتصال به pageTitle است که نمونهای از آنرا در قسمت قبل با معرفی اولین کامپوننت مشاهده کردهاید.
در ادامه نیاز است برای این قالب و view، یک کامپوننت را طراحی کنیم که متشکل است از یک کلاس TypeScript ایی مزین شده به Component. بنابراین فایل ts جدیدی را به نام product-list.component.ts به پوشهی App\products اضافه کنید؛ با این محتوا:
import { Component } from 'angular2/core'; @Component({ selector: 'pm-products', templateUrl: 'app/products/product-list.component.html' }) export class ProductListComponent { pageTitle: string = 'Product List'; }
با جزئیات نحوهی تعریف یک کامپوننت در قسمت قبل در حین معرفی کامپوننتها آشنا شدیم. در اینجا کلاس ProductListComponent با واژهی کلیدی export همراه است تا توسط module loader برنامه قابلیت بارگذاری را پیدا کند. همچنین خاصیت عمومی pageTitle نیز در آن تعریف شدهاست تا در قالب مرتبط مورد استفاده قرار گیرد.
سپس این کلاس، با decorator ویژهای به نام Component مزین شدهاست تا AngularJS 2.0 بداند که هدف از تعریف آن، ایجاد یک کامپوننت جدید است. مقدار selector آن که تشکیل دهندهی یک تگ HTML سفارشی متناظر با آن خواهد شد، به pm-products تنظیم شدهاست و اینبار بجای تعریف inline قالب آن به صورت یک رشته، از خاصیت templateUrl جهت معرفی مسیر فایل html قالبی که پیشتر آماده کردیم، کمک گرفته شدهاست.
نمایش کامپوننت لیست محصولات در صفحهی اصلی سایت
خوب، تا اینجا یک کامپوننت جدید را به نام لیست محصولات، ایجاد کردیم؛ اما چگونه باید آنرا نمایش دهیم؟
در قسمت قبل که کامپوننت ریشهی برنامه یا AppComponent را تعریف کردیم، نام selector آن را pm-app درنظر گرفتیم و در نهایت این directive سفارشی را به نحو ذیل در body صفحهی اصلی سایت نمایش دادیم:
<div> @RenderBody() <pm-app>Loading App...</pm-app> </div>
الف) تگ سفارشی این دایرکتیو جدید را به کامپوننت ریشهی سایت یا همان AppComponent اضافه میکنیم. بنابراین فایل app.component.ts را گشوده و سپس selector کامپوننت لیست محصولات را به قالب آن اضافه کنید:
import { Component } from 'angular2/core'; @Component({ selector: 'pm-app', template:` <div><h1>{{pageTitle}}</h1> <pm-products></pm-products> </div> ` }) export class AppComponent { pageTitle: string = "DNT AngularJS 2.0 APP"; }
ب) تا اینجا یک دایرکتیو جدید را به نام pm-products به یک کامپوننت دیگر اضافه کردهایم. اما این کامپوننت نمیداند که اطلاعات آنرا باید از کجا تامین کند. برای این منظور خاصیت جدیدی را به نام directives به لیست خاصیتهای Component ریشهی سایت اضافه میکنیم. این خاصیت، آرایهای از دایرکتیوهای سفارشی را قبول میکند:
directives: [ProductListComponent]
import { Component } from 'angular2/core'; import { ProductListComponent } from './products/product-list.component'; @Component({ selector: 'pm-app', template:` <div><h1>{{pageTitle}}</h1> <pm-products></pm-products> </div> `, directives: [ProductListComponent] }) export class AppComponent { pageTitle: string = "DNT AngularJS 2.0 APP"; }
این سه مرحله، مراحلی هستند که جهت افزودن هر دایرکتیو جدید به کامپوننتی مشخص، باید طی شوند.
خوب، اکنون اگر برنامه را اجرا کنیم، چنین خروجی را میتوان مشاهده کرد:
یک نکته
اگر برنامه را اجرا کردید و خروجی را مشاهده نکردید، مطمئن شوید که فایلهای ts شما کامپایل شدهاند. فشردن دکمهی ctrl+s مجدد در این فایلها، سبب کامپایل مجدد آنها میشوند و یا انتخاب گزینهی Build و سپس ReBuild solution نیز همینکار را انجام میدهد.
غنی سازی کامپوننتهای AngularJS 2.0 با data-binding
در AngularJS 2.0 عملیات binding، کار مدیریت ارتباطات بین یک کلاس کامپوننت و قالب آنرا انجام میدهد. نمونهای از آنرا پیشتر با خاصیت pageTitle و سپس نمایش آن در قالب کامپوننت متناظر با آن کلاس، مشاهده کردهاید. همچنین در اینجا یک قالب میتواند متدهای داخل کلاس کامپوننت خود را توسط رخدادها نیز فراخوانی کند.
به نحوهی نمایش {{pageTitle}} اصطلاحا interpolation میگویند. در اینجا خاصیت pageTitle اطلاعات خود را از کلاس کامپوننت دریافت میکند. به این نوع binding، انقیاد یک طرفه یا one-way binding نیز گفته میشوند؛ از خاصیت کلاس شروع شده و به قالب خاتمه مییابد.
ویژگی interpolation فراتر است از صرفا نمایش یک خاصیت و میتواند حاوی محاسبات نیز باشد:
{{'Title: ' + pageTitle}} {{2*20+1}}
{{'Title: ' + getTitle()}}
<h1>{{pageTitle}}</h1>
<h1 innerText={{pageTitle}}></h1>
بنابراین به صورت خلاصه هر زمانیکه نیاز به نمایش اطلاعات فقط خواندنی (one-way binding) داریم، ابتدا خاصیتی را در کلاس کامپوننت تعریف کرده و سپس مقدار این خاصیت را توسط interpolation، در قالب کامپوننت درج میکنیم. حین استفاده از interpolation نیازی به ذکر "" نیست.
در مورد مباحث تکمیلی binding در قسمتهای بعدی بیشتر بحث خواهیم کرد.
افزودن منطقی سفارشی به قالب یک کامپوننت
دایرکتیوها به صورت المانها و یا ویژگیهای سفارشی HTML، قابلیت توسعهی امکانات پیش فرض آنرا دارند. در اینجا میتوان دایرکتیوهای سفارشی خود را تولید کرد (مانند pm-products فوق) و یا از دایرکتیوهای توکار AngularJS 2.0 استفاده کرد. برای مثال ngIf* و ngFor* جزو structural directives توکار AngularJS 2.0 هستند. ستارهای که پیش از نام این دایرکتیوها قرار گرفتهاست، آنها را در گروه structural directives قرار میدهد.
کار دایرکتیوهای ساختاری، تغییر ساختار یا همان view کامپوننتها است؛ با افزودن، حذف و یا تغییر المانهای HTML تعریف شدهی در صفحه.
بررسی ngIf*
فایل قالب product-list.component.html را گشوده و تعریف جدول آنرا به نحو ذیل تغییر دهید:
<table class='table' *ngIf='products && products.length'>
برای نمونه عبارت انتساب داده شدهی به ngIf* در مثال فوق به این معنا است که اگر خاصیت و آرایهی products در کلاس کامپوننت این قالب تعریف شده بود و همچنین دارای اعضایی نیز بود، آنگاه این جدول را نمایش بده.
برای آزمایش آن، فایل product-list.component.ts را گشوده و خاصیت عمومی آرایهی products را به نحو ذیل به آن اضافه کنید:
import { Component } from 'angular2/core'; @Component({ selector: 'pm-products', templateUrl: 'app/products/product-list.component.html' }) export class ProductListComponent { pageTitle: string = 'Product List'; products: any[] = [ { "productId": 2, "productName": "Garden Cart", "productCode": "GDN-0023", "releaseDate": "March 18, 2016", "description": "15 gallon capacity rolling garden cart", "price": 32.99, "starRating": 4.2, "imageUrl": "app/assets/images/garden_cart.png" }, { "productId": 5, "productName": "Hammer", "productCode": "TBX-0048", "releaseDate": "May 21, 2016", "description": "Curved claw steel hammer", "price": 8.9, "starRating": 4.8, "imageUrl": "app/assets/images/rejon_Hammer.png" } ]; }
همچنین فعلا در اینجا اطلاعات را بجای دریافت از سرور، توسط آرایهی مشخصی از اشیاء تعریف کردهایم. این موارد را در قسمتهای بعدی بهبود خواهیم بخشید.
اکنون که خاصیت عمومی products تعریف شدهاست، امکان استفادهی از ngIf* ایی که پیشتر تعریف کردیم، میسر شدهاست. در این حالت اگر برنامه را اجرا کنید، قسمت table header تصویر قبلی نمایش سایت، هنوز نمایان است. یعنی ngIf* تعریف شده کار میکند؛ چون خاصیت products تعریف شدهاست و همچنین دارای اعضایی است.
برای آزمایش بیشتر، خاصیت products را کامنت کنید و یکبار نیز فایل ts آنرا ذخیره کنید تا فایل js متناظر با آن کامپایل شود. سپس مجددا برنامه را اجرا کنید. در این حالت دیگر نباید هدر جدول نمایان باشد؛ چون products تعریف نشدهاست.
بررسی ngFor*
تا اینجا بر اساس داشتن لیستی از محصولات یا عدم آن، جدول متناظری را نمایش داده و یا مخفی کردیم. اما این جدول هنوز فاقد ردیفهای نمایش اعضای آرایهی products است.
برای این منظور مجددا فایل قالب product-list.component.html را گشوده و سپس بدنهی جدول را به نحو ذیل تکمیل کنید:
<tbody> <tr *ngFor='#product of products'> <td></td> <td>{{ product.productName }}</td> <td>{{ product.productCode }}</td> <td>{{ product.releaseDate }}</td> <td>{{ product.price }}</td> <td>{{ product.starRating }}</td> </tr> </tbody>
بنابراین ابتدا قسمتی از عناصر HTML را طوری کنار هم قرار میدهیم که جمع آنها یک تک آیتم را تشکیل دهند. سپس با استفاده از ngFor* به AngularJS 2.0 اعلام میکنیم که این قطعه را به ازای عناصر لیست دریافتی، تکرار و رندر کند.
برای نمونه در مثال فوق میخواهیم ردیفهای جدول تکرار شوند. بنابراین هر ردیف را به عنوان یک قطعهی تکرار شوندهی توسط ngFor* مشخص میکنیم. به این ترتیب این ردیف و عناصر فرزند آن، به ازای تک تک محصولات موجود در آرایهی products، تکرار خواهند شد.
علامت # در اینجا (product#) یک متغیر محلی را تعریف میکند که تنها در قالب جاری قابل استفاده خواهد بود و همچنین فقط در فرزندان tr تعریف شده قابل دسترسی هستند.
به علاوه در اینجا بجای in از of استفاده شدهاست. این of از ES 6 گرفته شدهاست. زمانیکه از حلقهی جدید for...of استفاده میشود، متغیر محلی product حاوی یک عنصر از لیست product خواهد بود؛ اما اگر از حلقهی قدیمی for...in استفاده میشد، تنها ایندکس عددی این عناصر در دسترس قرار میگرفتند. به همین جهت است که در این حلقه، اکنون product.productName به نام محصول آن عنصر آرایهی دریافتی اشاره میکند و قابل استفاده است.
تا اینجا اگر برنامه را اجرا کنید، چنین خروجی را مشاهده خواهید کرد:
کدهای کامل این قسمت را از اینجا میتوانید دریافت کنید: MVC5Angular2.part3.zip
خلاصهی بحث
از inline templateها جهت معرفی قالبهای کوتاه استفاده میشود. در اینجا از "" برای معرفی قالب یک سطری و یا از back tickهای ES 6، برای تعریف قالبهای چندسطری استفاده خواهد شد. برای قالبهای مفصلتر، بهتر است Linked templateها استفاده شود؛ با پشتیبانی کامل ادیتورهای موجود از لحاظ تکمیل و بررسی کدها.
برای استفاده از یک کامپوننت در کامپوننتی دیگر، نام selector آنرا به صورت یک المان جدید HTML در قالب دیگری ذکر کرده و سپس با استفاده از خاصیت directives، نام کلاس متناظر با آنرا نیز ذکر میکنیم. همچنین کار import ماژول آن نیز باید در ابتدای فایل صورت گیرد.
جهت غنی سازی قالبها و کامپوننتها و نمایش اطلاعات فقط خواندنی میتوان از binding یک طرفهی ویژهای به نام interpolation استفاده کرد. کار آن اتصال یک خاصیت عمومی کلاس کامپوننت، به قالب آن است. interpolation توسط {{}} تعریف میشود و میتواند شامل محاسبات نیز باشد.
همچنین در ادامهی بحث، نحوهی کار با دو دایرکتیو توکار ساختاری AngularJS 2.0 را نیز بررسی کردیم. این دایرکتیوهای ساختاری نیاز است با ستاره شروع شوند و عبارت انتساب داده شدهی به آنها باید داخل "" قرار گیرد (برخلاف interpolation که نیازی به اینکار ندارد). از ngIf* برای حذف یا افزودن یک المان و فرزندان آن از/به DOM استفاده میشود. اگر عبارت منتسب به آن به true ارزیابی شود، این المان از صفحه حذف خواهد شد. از ngFor* برای تکرار المانی مشخص به همراه فرزندان آن به تعداد اعضای لیستی که برای آن تعیین میگردد، استفاده میشود. متغیر محلی این پیمایشگر با # مشخص شده و حلقهی آن با of بجای in تعریف میشود.
معرفی DNTPersianComponents.Blazor
حتما تا به حال در وب سایتهای زیادی قسمت هایی را دیده اید که چیدمان عناصر آن به شکل زیر است:
این گونه چیدمان را حتما در منوی Start ویندوز 8 بارها دیدهاید! عناصر تشکیل دهندهی این شکل از چیدمان، میتوانند یک سری عکس باشند که تشکیل یک گالری عکس را دادهاند و یا یک سری div که محتوای پستهای یک وبلاگ را در خود جای دادهاند. چیزی که این شکل از چیدمان عناصر را نسبت به چیدمانهای معمول متمایز میکند این است که طول و عرض هر یک از این عناصر با یکدیگر متفاوت است و هدف از این گونه چیدمان آن است که این عناصر در فضایی که به آنها اختصاص داده شده است، به صورت بهینه قرار گیرند تا کمترین فضا هدر رود.
برای اعمال این شکل از چیدمان در دنیای وب افزونههای زیادی بر فراز کتاب خانهی jQuery تدارک دیده شده است که از جمله مطرحترین آنها میتوان به افزونه های Isotope ، Masonry و Gridster اشاره کرد.
افزونهی Isotope مزایایی را برای من در پی داشت و این افزونه را برای انجام کارهای خود، مناسب دیدم. نکتهی مهم اینجا است که هدف من بررسی Isotope نیست، چرا که اگر به وب سایت آن مراجعه کنید، با کوهی از مستندات مواجه میشوید که چگونه از آن در وب سایتهای معمولی استفاده کنید.
در این مقاله قصد من این است که نشان دهم چگونه از افزونهی Isotope در AngularJS استفاده کنیم؛ چگونه چیدمان آن را راست به چپ کنیم و چگونه آن را با محیطهای واکنش گرا (Responsive) سازگار کنیم.
فرض کنید در یک وب سایت قصد داریم اطلاعات یک سری مطلب خبری را از سرور، به فرمت JSON دریافت کرده و نمایش دهیم. در AngularJS شیوهی کار بدین صورت است که اطلاعاتی که به فرمت JSON هستند را با استفاده از directive ایی به نام ng-repeat پیمایش کرده و آنها را نمایش دهیم. حال اگر بخواهیم چیدمان مطالب را با استفاده از Isotope تغییر دهیم، میبینیم که هیچ چیزی نمایش داده نمیشود. دلیل آن بر میگردد به مراحل کامپایل کردن AngularJS و نامشخص بودن زمان اعمال چیدمان Isotope به عناصر است.
در AngularJS هنگامیکه با دستکاری DOM سر و کار پیدا میکنیم، معمولا باید به سراغ Directiveها رفت و یک Directive سفارشی برای کار با Isotope تعریف کرد تا با مکانیزمهای Angular سازگار باشد. خوشبختانه Directive Isotope برای Angular موجود میباشد. نکتهی مهم این است که این Directive برای نگارش 1 افزونهی Isotope نوشته شده است. البته با نگارش 2 هم کار میکند که من برای انجام کار خود نسخهی 1 را ترجیح دادم استفاده کنم.
نکتهی بعدی که باید رعایت شود این است که چیدمان عناصر باید از راست به چپ شوند. خوشبختانه این کار در نسخهی 1 Isotope با تغییر کوچکی در سورس Isotope و تغییر یک تابع انجام میشود. گویا نسخهی دوم امکان پیش فرضی را برای این کار دارد، اما نتوانستم آن را به خوبی پیاده سازی کنم و به همین دلیل ترجیح دادم از همان نسخهی اول استفاده کنم.
برای اینکه در هنگام جابه جا شدن عناصر، انیمیشنها نیز از راست به چپ انجام شوند، باید cssهای زیر را نیز اعمال نمود:
.isotope .isotope-item { -webkit-transition-property: right, top, -webkit-transform, opacity; -moz-transition-property: right, top, -moz-transform, opacity; -ms-transition-property: right, top, -ms-transform, opacity; -o-transition-property: right, top, -o-transform, opacity; transition-property: right, top, transform, opacity; }
Responsive بودن این عناصر مسئلهی دیگری است که باید حل گردد. امروزه اکثر فریم ورکهای مطرح css، واکنشگرا نیز هستند و برای پشتیبانی از سایزهای متفاوت صفحه نمایش، تدابیری در نظر گرفتهاند. اساس کار واکنش گرا بودن این فریم ورکها در تعیین ابعاد عناصر، بیان ابعاد به صورت درصدی است. مثلا فلان عرض div برابر 50% باشد بدین معناست که همیشه عرض این div نصف عرض عنصر والد آن باشد.
متاسفانه Isotope میانهی چندانی با این ابعاد درصدی ندارد و باید عرض عناصر به صورت دقیق و بر حسب پیکسل بیان شود. البته نسخهی جدید آن و یا حتی پلاگین هایی برای کار با ابعاد درصدی نیز تدارک دیده شده است که به شخصه به نتیجهی با کیفیتی نرسیدم.
@media (min-width: 768px) and (max-width: 980px) { .card { width: 320px; } } @media (min-width: 980px) and (max-width: 1200px) { .card { width: 260px; } } @media (min-width: 1200px) { .card { width: 340px; } }
app.directive('imageOnload', function () { return { restrict: 'A', link: function (scope, element, attrs) { element.bind('load', function () { scope.$emit('iso-method', { name: 'reLayout', params: null }); // call reLayout isotope methode prevent overlaaping the items }); } }; });
$(window).resize(function () { $timeout(function myfunction() { $scope.$broadcast('iso-method', { name: 'reLayout', params: null }); // call reLayout isotope methode prevent overlaaping the items },1000); });