نگاهی به لیست کاری تیم C# 7
در مقاله قبلی، درباره نحوه نصب و راه اندازی اولین پروژه Xamarin Forms کمی صحبت کردیم. حال وقت آن رسیدهاست که درباره ساختار اپلیکیشنهای Xamarin Forms بیشتر بحث کنیم. در سیستم عاملهای مختلف، رابطهای کاربری با اسامی مختلفی مانند Control ، Widget ، View و Element صدا زده میشوند که هدف تمامی آنها نمایش و ارتباط با کاربر میباشد. در Xamarin Forms به تمام عناصری که در صفحه نمایش نشان داده میشوند، Visual Elements گفته میشود؛ که در سه گروه بندی اصلی قرار میگیرند:
· Page
· Layout
· View
هر چیزی که فضایی را در صفحه اشغال کند، یک Visual Element است. Xamarin Forms از یک ساختار سلسه مراتبی Parent-Child برای UI استفاده میکند. به طور مثال یک اپلیکیشن را در نظر بگیرید. هر اپلیکیشن به طور کلی از چندین صفحه تشکیل شده است. هر Page برای چینش کنترلهای مختلف، از یک سری Layout استفاده میکند و هر Layout هم شامل چندین View مختلف میباشد.
در مقاله قبلی، پروژه اولیه خود را ساختیم. اگر به پروژه Shared مراجعه کنیم، خواهیم دید که این پروژه دارای کلاسی به نام App است. اگر به خاطر داشته باشید، گفتیم که این کلاس اولین Page درون اپلیکیشن را مشخص میکند و همچنین میتوان برای مدیریت LifeCycle اپلیکیشن مانند OnStart و ... از آن استفاده کرد. در متد سازنده، صفحهای به نام MainPage به عنوان اولین صفحه برنامه مشخص شده بود. به کدهای این صفحه بار دیگر نگاهی کنیم تا بتوانیم کمی بر روی این کدها توضیحاتی را ارائه دهیم:
<?xml version="1.0" encoding="utf-8" ?> <ContentPage xmlns="http://xamarin.com/schemas/2014/forms" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" xmlns:local="clr-namespace:PreviewerTest" x:Class="PreviewerTest.MainPage"> <Label Text="Welcome to Xamarin Forms!" VerticalOptions="Center" HorizontalOptions="Center" /> </ContentPage>
همانطور که میبینید ساختار سلسه مراتبی را به خوبی میتوانید در این کدها مشاهده کنید. در وهله اول یک ContentPage را به عنوان والد اصلی مشاهده میکنید. Page ها در Xamarin Forms انواع مختلفی دارند که ContentPage یکی از آنهاست و از آن میتوانید به عنوان یک صفحه ساده استفاده کنید.
در درون این صفحه یک Label را به عنوان Child صفحه مشاهده میکنید (تمامی کنترلها در زمارین در زیر گروه View قرار میگیرند).
نتیجه این کدها صفحهای ساده با یک لیبل است که تمامی صفحه را اشغال کردهاست. اگر شما View دیگری را در زیر این لیبل اضافه کنید خواهید دید که این دو، روی هم میافتند و شما نمیتوانید کنترل زیر آن را مشاهده کنید. همانطور که در بالا گفتیم زمارین از المنتی به نام Layout برای چینش عناصر استفاده میکند. Layout های مختلفی در زمارین وجود دارند که هر کدام به طُرق مختلفی این عناصر را در کنار هم میچینند. یکی از آنها StackLayout میباشد.
<?xml version="1.0" encoding="utf-8" ?> <ContentPage xmlns="http://xamarin.com/schemas/2014/forms" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" xmlns:local="clr-namespace:XamarinSample" x:Class="XamarinSample.MainPage"> <StackLayout> <Label Text="Welcome to Xamarin Forms!" VerticalOptions="Center" HorizontalOptions="Center" /> <Button Text="Ok!"/> </StackLayout> </ContentPage>
StackLayout عناصر فرزند خود را به صورت افقی و عمودی در کنار هم در صفحه میچیند.
اگر به خاطر داشته باشید، در هنگام ساخت پروژه زمارین چندین پروژه برای پلتفرمهای مختلف در کنار آن ساخته شد. پروژه XamarinSample.Android برای ساخت و مدیریت پروژه در پلتفرم اندروید، مورد استفاده قرار میگیرد. همانطور که گفتیم کدهای درون این پروژهها با پروژه Shared ادغام شده و با هم اجرا خواهند شد. وقت آن رسیده که سری به کدهای آن بزنیم و نحوهی اجرای پروژه Shared را توسط پروژه اندروید ببینیم.
وقتی پروژه اندروید را باز کنید با کلاسی به نام MainActivity مواجه خواهید شد. این کلاس وظیفه ایجاد Activity اصلی برنامه را دارد.
namespace XamarinSample.Droid { [Activity(Label = "XamarinSample", Icon = "@drawable/icon", Theme = "@style/MainTheme", MainLauncher = true, ConfigurationChanges = ConfigChanges.ScreenSize | ConfigChanges.Orientation)] public class MainActivity: global::Xamarin.Forms.Platform.Android.FormsAppCompatActivity { protected override void OnCreate(Bundle bundle) { TabLayoutResource = Resource.Layout.Tabbar; ToolbarResource = Resource.Layout.Toolbar; base.OnCreate(bundle); global::Xamarin.Forms.Forms.Init(this, bundle); LoadApplication(new XamarinSample.App()); } } }
در Attribute بالای سر کلاس، برخی از ویژگیها مانند تم، آیکن، سایز و ... مقداردهی شدهاند. همچنین باعث میشود که در صورت تغییر Orientation و سایز، Activity از اول ساخته نشود. در متد OnCreate علاوه بر استایل دهی به TabLayout و ToolBar ها متدی به نام Forms.Init صدا زده شده است. این متد استاتیک که در تمامی پروژهها فراخوانی میشود، سیستم Xamarin Forms را در هر کدام از پلت فرمها بارگزاری میکند.
کتابخانه ProgressButtonStyles
A set of flat and 3D progress button styles where the button itself serves as a progress indicator. 3D styles are used for showing the progress indication on one side of the button while rotating the button in perspective. Demo
WAI-ARIA که برگرفته از Web Accessibility Initiative - Accessible Rich internet Application است به معنی برنامهی اینترنتی تعامل گرا با خاصیت دسترسی پذیری بالا میباشد و یک راهنماست که توسط کنسرسیوم وب (+ ) معرفی گشته است تا وب سایتها با رعایت این قوانین، دسترسی سایت خود را بالاتر ببرند. این قوانین به خصوص برای سایتهایی با محتوای پویا هستند که از فناوریهایی چون Ajax,Javascrip, HTML و دیگر فناوریهای مرتبط استفاده میکنند.
امروزه طراحان وب بیش از هر وقتی از فناوریهای سمت کلاینت چون جاوااسکریپت برای ساخت رابطهای کاربری استفاده میکنند که html به تنها قادر به ایجاد آنها نیست. یکی از تکنیکهای جاوااسکریپت، دریافت محتوای جدید و به روزآوری قسمتی از صفحات وب است بدون اینکه مجددا کل صفحه از وب سرور درخواست گردد که به این تکنیک Rich Internet Application هم میگویند. تا به اینجای کار هیچ مشکلی نیست و خوب هم هست؛ ولی مشکلی که در این بین وجود دارد این است که این نوع تکنیکها باعث از بین رفتن خاصیت دسترسی پذیری معلولین میگردند. معلولینی که از صفحه خوان ها استفاده میکنند یا به دلیل معلولیتهای خود قادر به حرکت دادن ماوس نیستند.
ARIA با استفاده از خصوصیتها Properties، نقشها Roles و وضعیتها States به طراحان برنامههای وب و سازندگان فناوریهای یاری رسان، اجازه میدهد که با ابزارهای کمکی معلولان ارتباط برقرار کنیم و یک صفحهی وب ساده را به یک صفحهی پویا تبدیل کنیم. ARIA تنها یک استاندارد برای وب نیست، بلکه یک فناوری چند پلتفرمه است که برای بازیهای رایانهای، موبایلها، دستگاههای سرگرمی و سلامتی و دیگر انواع برنامهها نیز تعریف شده است.
فریمورک ARIA
خود HTML به تنهایی نمیتواند نقشهای هر المان و ارتباط بین آنها را به درستی بیان کند و به این منظور ARIA به کمک میآید. با استفاده از نقشها میتوان هدف هر المان را مشخص کرد و با استفاده از خصوصیات ARIA نحوهی عملکرد آنها را تعریف کرد.
نقش ها
نقشها طبق مستندات کنسرسیوم وب بر 4 نوع هستند:
- Abstract Roles
- Widget Roles
- Document Roles
- Landmark Roles
Banner | این
قسمت که عموما برای اجزای مهمی مثل هدر سایت قرار میگیرد و شامل معرفی وب
سایت هست و در همهی صفحات وجود دارد که شامل لوگو، اطلاعات عمومی سایت و
اسپانسرها و ... میگردد و بسیار مهم است که تنها یکبار در صفحهی وب به کار
برود و تکرار آن پرهیز شود. |
Main | این
نقش به محتوای اصلی وب سایت اشاره میکند و نباید بیشتر از یکبار در هر صفحهی وب به کار برود و عموما بهتر است این خصوصیت در تگ div قرار گیرد:<div Role="main"></div> <main role="main">..... |
Navigation | اشاره به یک ناحیه پر از المانهای لینک برای ارتباط با صفحات دیگر |
Complementary | مشخص سازی ناحیهای که اطلاعات اضافی دربارهی محتوای اصلی سایت دارد؛ مانند بخش مقالات مرتبط، آخرین کامنتها و ... |
ContentInfo | این نقش که بیشتر برای فوتر مناسب است برای محتوایی به کار میرود که در آن به قوانین کپی رایت و ... اشاره میشود. |
form | برای اشاره به فرمها که دارای قسمتهای ورودی کاربر هستند. |
search | در صورتیکه فرمی دارید و از آن برای گزینهی جست و جو استفاده میکنید، از این نقش استفاده کنید. |
application | برای
اینکه وب سایت خود را به صورت یک وب اپلیکیشن معرفی کنید؛ تا یک صفحه وب معمولی،
استفاده میشود و برای وب سایتهای قدیمی یا با حالت سنتی توصیه نمیشود و
به برنامههای کمکیار معلولین میگوند که از حالت عادی به حالت
application سوئیچ کنند؛ پس با دقت بیشتری باید از این گزینه استفاده کرد. |
خصوصیتها و وضعیت ها
در حالیکه از نقشها برای معرفی هر المان یا تگ استفاده میکنید؛ خصویتها و وضعیتها به کاربر اطلاعات اضافی میدهند که چگونه ز آن استفاده کنند. برای معرفی خصوصیتها و وضعیتها از یک خصوصیت که با -aria شروع میشود استفاده کنید. از معروفترین آنها خصوصیت aria-required و وضعیت aria-checked میباشند، تا به ترتیب به کاربر اعلام کنید این تگ نیاز به پر شدن دارد، یا المانی نیاز به تغییر وضعیت انتخابی دارد.
نحوهی استفاده از آنها به شکل زیر است:
<div id="some-id" class="some-class" aria-live="assertive"><div>
ساخت ارتباطات میان المانها با خصوصیتهای ارتباطی
مثال شماره یک
<div role="main" aria-labelledby="some-id"> <h1 id="some-id">This Is A Heading</h1> Main content... </div>
مثال شماره دو
در این مثال هر گروهی از المانها یک برچسب و بعضی المانها یک برچسب اختصاصی دارند که توسط خصوصیت معرفی شده aria-labelldby کامل شدهاند:
<div id="billing">Billing Address</div> <div> <div id="name">Name</div> <input type="text" aria-labelledby="name billing"/> </div> <div> <div id="address">Address</div> <input type="text" aria-labelledby="address billing"/> </div>
مثال شماره سه
در این مثال گروهی از radio buttonها با برچسبشان ارتباط برقرار میکنند.
<div id="radio_label">My radio label</div> <ul role="radiogroup" aria-labelledby="radio_label"> <li role="radio">Item #1</li> <li role="radio">Item #2</li> <li role="radio">Item #3</li> </ul>
خصوصیتها و وضعیتهای aria را با چرخهی فعالیتهای صفحه به روز کنید
در صورتیکه چرخهی فعالیت صفحهی شما تغییر میکند و تگها نیاز به مقادیر جدیدی از aria دارند، حتما این مقادیر را هم به نسبت تغییراتی که در صفحه زخ میدهد، تغییر دهید تا وضعیت بحرانی برای کاربر به خصوص در حین کار با فرمها و ... پیش نیاید.
هر aria را دوباره استفاده نکنید
امروزه به خصوص با آمدن html5 و ویژگیهایی چون تگهای مفهومی، کار بسیار راحتتر شدهاست و مرورگر به طور خودکار میتواند aria را بر روی بعضی از المانها پیاده کند. به عنوان نمونه:
<form></form> <form role="form"></form>
امروزه شاهد پیشرفت فناوری در همهی عرصهها هستیم و همیشه این پیشرفتها ما را ذوق زده کردهاند، ولی یکی از بی نظیرترین استفادههای فناوری روز، استفاده در صنایع سلامتی است که نه تنها ما را ذوق زده میکند، بلکه از لحاظ احساسی هم ما را به وجد میآورند و جزء زیباترین نتایج فناوری میباشند. بسیاری از شرکتها چون گوگل در این راستا فعالیتهای زیادی کردهاند تا بتوانند سلامت جامعه را کنترل کنند، از ساخت لنز چشمی برای کنترل دیابت گرفته تا ساخت قاشق عذای خوری برای بیماران پارکینسون، ولی استفادههای ساده از مسائلی مانند بالا به افراد معلول این مژده را میدهد که ما آنها را فراموش نکردهایم.
در زامارین 2 نوع layout داریم
FindViewbyid در پارامتر ورودی خود از طریق Resource.id، نام کنترل را دریافت نموده و بصورت Object باز میگرداند که بایستی نتیجه خروجی را به کلاس همان کنترل Cast نماییم:
Button btn = FindViewById<Button>(Resource.Id.button1);
FindViewById<EditText>(Resource.Id.txtname).Text = "";
هر layout یک اکتیویتی مربوط به خودش را دارد. اگر بخواهیم بین فرمهای مختلف حرکت کنیم، به ازای هر فرم، یک Activity کنترل کننده همان فرم را اضافه میکنیم. در یک Activity با ارسال سیگنالی به نام Intent که پارامتر اول آن، Activity مبدا یا This و پارامتر دوم آن، Activity مقصد میباشد. سپس به سیستم عامل اطلاع میدهیم که میخواهیم این سیگنال را ارسال کنیم و ارسال آن با StartActivity میباشد:
Intent _intent = new Intent(this, typeof(Activity2)); StartActivity(_intent);
ارسال پارامتر
جهت ارسال دادههای معمولی bool,double,string,float,long,... و آرایه ای از آن، از دستور PutExtra استفاده میشود:
Intent _intent = new Intent(this, typeof(Activity2)); string Test="Vlaueone"; _intent.PutExtra("mainactivity", Test); StartActivity(_intent);
گرفتن پارامتر
در Activity مقصد، اطلاعات مربوط به سیگنال ارسال شده، در مقادیر Global intent ذخیره میشود و بر اساس نوع داده ارسال شده، تابع GetExtra را مینویسیم:
string Getintent = Intent.GetStringExtra("mainactivity");
ارسال object از کلاسها بین Activityها
در زامارین، object کلاسها را به یک string استاندارد بصورت json تبدیل میکنیم و به این عملیات Serialization گفته میشود. این کار نیز توسط کتابخانههای مختلفی انجام میشود مانند NewtonSoft. سپس رشته json ایجاد شده را با تابع PutExtra، به همراه سیگنال Intent، به Activity دوم پاس میدهیم:
List<Product> products; products = new List<Product>(); Product p = new Product { name = FindViewById<EditText>(Resource.Id.mainedittextname).Text, price = Convert.ToInt32(FindViewById<EditText>(Resource.Id.mainedittextprice).Text) }; products.Add(p); Intent _intent = new Intent(this, typeof(Activity2)); _intent.PutExtra("products", JsonConvert.SerializeObject(products)); StartActivity(_intent);
var p = JsonConvert.DeserializeObject<List<Product>>(Intent.GetStringExtra("products")); foreach (var item in p) { Toast.MakeText(this, $" {item.name} , {item.price}", ToastLength.Long).Show(); };
در ادامه کلاسهای مورد نظر را جهت تعریف در دایرکتوری Model ایجاد میکنیم. مثل کلاس Product که بصورت Public نیز میباشد. از منوی Tools -> Nuget Package Management -> Manage nuget Package for Solution را انتخاب و سپس NewtonSoft را اضافه میکنیم.
AlertDialog زیر کلاس Dialog است که میتواند یک، دو و یا سه دکمه را نمایش دهد. اگر فقط میخواهید یک رشته را در این کادر محاوره ای نمایش دهید، از روش SetMessage استفاده کنید. قطعه کد زیر میتواند برای ایجاد یک AlertDialog ساده با دو دکمه حذف و لغو استفاده شود:
//set alert for executing the task AlertDialog.Builder alert = new AlertDialog.Builder(this); alert.SetTitle("Confirm delete"); alert.SetMessage("Lorem ipsum dolor sit amet, consectetuer adipiscing elit."); alert.SetPositiveButton("Delete", (senderAlert, args) => { Toast.MakeText(this, "Deleted!", ToastLength.Short).Show(); }); alert.SetNegativeButton("Cancel", (senderAlert, args) => { Toast.MakeText(this, "Cancelled!", ToastLength.Short).Show(); }); Dialog dialog = alert.Create(); dialog.Show();
Fragment
public class DialogFragmentActivity : DialogFragment { public override View OnCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { var dlg = inflater.Inflate(Resource.Layout.layoutDialog, container, false); return dlg; } }
DialogFragmentActivity dlg = new DialogFragmentActivity (); dlg.Show(this.FragmentManager, "Fragment");
public override void OnStart() { base.OnStart(); Dialog dialog = Dialog; if (dialog != null) { dialog.Window.SetLayout(ViewGroup.LayoutParams.MatchParent, ViewGroup.LayoutParams.WrapContent); dialog.Window.SetBackgroundDrawable(new ColorDrawable(Color.Transparent)); } }
public override Dialog OnCreateDialog(Bundle savedInstanceState) { Dialog NotTitle = base.OnCreateDialog(savedInstanceState); NotTitle.Window.RequestFeature(WindowFeatures.NoTitle); return NotTitle; }
<form action="/rest/login" method="POST"> <input name="username" required> <input type="password" name="password" required> </form>
محتوا یا content به معنای المانهایی هستند که درون یک المان قرار میگیرند. برای مثال در اینجا المان <form> دارای محتوایی متشکل از دو المان <input> است و دون المان <input> دارای محتوایی نیستند.
از ویژگیها و یا attributes، برای ارائهی اطلاعات بیشتر و یا تعیین حالت عناصر استفاده میشود. برای نمونه در اینجا المان <form> دارای دو ویژگی action و method است که برای ارائهی اطلاعاتی بیشتر جهت تعیین روش و محل ارسال اطلاعات به سرور از آنها استفاده میشود. همچنین میتوان ویژگیهای سفارشی را نیز توسط ویژگیهای شروع شدهی با -data نیز به المانها اضافه کرد که جزئی از استاندارد HTML 5 است. هرچند میتوان ویژگی کاملا غیرمتعارفی مانند myproject-uuid را نیز به یک المان اضافه کرد. این مورد مشکل خاصی را ایجاد نکرده و صفحه بدون مشکل رندر میشود؛ اما یک چنین صفحهای دیگر به عنوان یک صفحهی استاندارد HTML ارزیابی نخواهد شد؛ از این جهت که از ویژگی استفاده کردهاست که در هیچکدام از استانداردهای HTML ذکر نشدهاست.
آخرین نگارش HTML5 به همراه 4 ویژگی جدید دیگر نیز هست:
- Boolean attribute : مانند ویژگی required که به المانهای <input> قابل انتساب است. حضور این نوع ویژگیها بدون مقداری در یک المان
<input name=username required>
نمونهی دیگری از این نوع ویژگیها، ویژگی hidden است که به HTML 5.1 اضافه شدهاست و اگر به عنصری اضافه شود، آن المان رندر نخواهد شد.
- unquoted attribute: به این معنا که میتوان "" را از اطراف مقدار یک ویژگی حذف کرد:
<input name=username required>
- single-quoted and double-quoted attributes: که به معنای استفاده از "" و یا '' جهت تعیین مقدار یک ویژگی است.
تفاوت attributes با خواص المانها چیست؟
Attributes در تعریف تگ HTML یک عنصر ظاهر میشوند اما خواص، جزئی از تعریف شیء یک عنصر هستند.
<div class="bold">I'm a bold element</div>
<a href="http://www.site.com/blog/">Read the blog</a>
<script> document.querySelector('A').href = 'https://www.dntips.ir'; </script>
- برای مثال واژهی class در زبان جاوا اسکریپت یک واژهی کلیدی است. به همین جهت در اینجا اگر بخواهیم مقدار ویژگی class را تغییر دهیم باید از خاصیت className آن المان استفاده کنیم.
- مورد دوم ویژگی checked المانهای radio و checkbox است. این ویژگی فقط در ابتدای کار این المانها به آنها متصل میشود:
<input type="checkbox" checked> <script> // this does not remove the checked attribute document.querySelector('INPUT').checked = false; </script>
- تمام ویژگیهای غیراستانداردی که تعریف شوند، دارای خاصیت متناظری در آن المان نخواهند بود، اما به آنها expando properties گفته میشود.
یافتن المانها بر اساس ویژگیهای آنها
از آنجائیکه attribute selectors در استاندارد W3C CSS 2 معرفی شدهاند، امکان کار با آنها از زمان IE 8.0 میسر بودهاست و برای کار با آنها الزاما نیازی به استفاده از jQuery نیست!
یافتن المانها بر اساس نام ویژگیها
<form action="/submitHandler" method="POST"> <input name="first-name"> <input name="last-name" required> <input type="email" name="email" required> <button disabled>submit</button> </form>
var $result = $('[required], [disabled]');
var result = document.querySelectorAll('[required], [disabled]');
در اینجا باید دقت داشت که این جستجو صرفا بر اساس نام ویژگیها انجام میشود؛ حتی اگر این ویژگی دارای مقداری نباشد:
<div class="bold">I'm bold</div> <span class>I'm not</span>
var result = document.querySelectorAll('[class]');
یافتن المانها بر اساس نام و مقدار ویژگیها
<section> <h2>Sites</h2> <ul> <li> <a href="https://www.dntips.ir/">dotnettips</a> </li> <li> <a href="https://google.com/">Google</a> </li> </ul> </section>
var $result = $('A[href="https://www.dntips.ir/"]');
var result = document.querySelectorAll('A[href="https://www.dntips.ir/"]');
و یا اگر اینبار بخواهیم تمام ویژگیهای کلاسی که دارای مقدار هستند را انتخاب کنیم، روش آن با استفاده از exclusion selector به صورت زیر است:
var result = document.querySelectorAll('[class]:not([class=""]');
یافتن المانها بر اساس نام و قسمتی از مقدار ویژگیها
<a href="https://www.dntips.ir/">home page</a> <a href="https://www.dntips.ir/postsarchive">articles</a> <a href="https://www.dntips.ir/newsarchive">news</a>
var result = document.querySelectorAll('A[href*="www.dotnettips.info"]');
یافتن المانها بر اساس نام و کلمهای مشخص در مقدار ویژگیها
<div class="one two three">1 2 3</div> <div class="onetwothree">123</div>
var result = document.querySelectorAll('[class∼=two]');
در اینجا نوع دیگری از کوئری را هم میتوان مطرح کرد: آیا المانی مشخص، دارای کلاس two است؟
روش انجام آن در jQuery به صورت زیر است:
var hasTwoClass = $divEl.hasClass('two');
var hasTwoClass = divEl.classList.contains('two');
همچنین خاصیت classList به همراه متدهای استاندارد add و remove نیز هست که معادل متدهای addClass و removeClass جیکوئری هستند.
divEl.classList.remove('red'); divEl.classList.add('blue');
// removes "hide" class divEl.classList.toggle('hide'); // re-adds "hide" class divEl.classList.toggle('hide');
یافتن المانها بر اساس نام و شروع یا خاتمهی عبارتی مشخص در مقدار ویژگیها
<img id="cat" src="/images/cat.gif"> <img id="zebra" src="zebra.gif"> <a href="#zebra">watch the zebra</a> <a href="/logout">logout</a>
var result = document.querySelectorAll('A[href^="#"], [src$=".gif"]');
خواندن مقادیر ویژگیها
<input type="password" name="user-password" required>
// returns "password" $inputEl.attr('type'); // returns "true" $inputEl.is('[required]');
// returns "password" inputEl.getAttribute('type'); // returns "true" inputEl.hasAttribute('required');
تغییر مقدار ویژگیها
<input name="temp" required>
روش انجام اینکار در جیکوئری:
$inputEl .attr('type', 'email') // #1 .removeAttr('required') // #2 .attr('name', 'userEmail'); // #3
inputEl.setAttribute('type', 'email'); // #1 inputEl.removeAttribute('required'); // #2 inputEl.setAttribute('name', 'userEmail'); // #3
به همین جهت اگر میخواهید رزومهی غنیتری را ارائه دهید، فراگیری React میتواند موقعیتهای شغلی بیشتری را نصیب شما کند.
ساختار کلی یک برنامهی React
کامپوننتها (جزئی از یک رابط کاربری) قلب هر برنامهی React ای را تشکیل میدهند. برای ساخت یک برنامهی React، تعدادی کامپوننت مستقل را تهیه و با هم ترکیب میکنیم تا به رابط کاربری نهایی برسیم.
هر برنامهی React، حداقل از یک کامپوننت تشکیل میشود که به آن Root component هم میگویند. این کامپوننت بیانگر کل برنامهاست و دربرگیرندهی مابقی Child components برنامه است. بنابراین ساختار هر برنامهی React، شبیه به درختی از کامپوننتها است. اگر با Angular 2 به بعد کار کرده باشید، این مفهوم برای شما آشنا است.
یک مثال: فرض کنید میخواهیم UI برنامهای را به مانند رابط کاربری Twitter، ایجاد کنیم. هر قسمت یک صفحهی توئیتر، به کامپوننتهایی شکسته میشود؛ مانند منوی راهبری، نمایش پروفایل شخص، نمایش لیست آخرین اخبار مورد علاقهی شخص و نمایش فید. اگر بخواهیم این ساختار را توسط یک برنامهی React شبیه سازی کنیم، در بالاترین سطح، کامپوننت root را خواهیم داشت که کار ترکیب و نمایش سایر کامپوننتهای برنامه مانند nav bar ، trends ، profile و feed را انجام میدهد. اکنون در این ساختار ایجاد شده، برای مثال کامپوننت feed نیز میتواند از چندین کامپوننت مجزا تشکیل شود؛ مانند کامپوننتهای tweet و like.
بنابراین هر کامپوننت، قسمتی از UI را تشکیل میدهد. هر کدام از آنها به صورت مجزای از دیگری ساخته شده و سپس در کنار هم قرار میگیرند تا UI نهایی را شکل دهند:
هر کامپوننت در React به صورت یک کلاس ES6، با ساختاری که دارای یک شیء state و متد render است، تشکیل میشود:
class Tweet { state = {}; render() { } }
مزیت کارکردن با Virtual DOM، سادگی ایجاد، تغییر و به روز رسانی آن در مقایسه با DOM واقعی است که در نهایت کار رندر عناصر UI را در مرورگر انجام میدهد. زمانیکه در state کامپوننتی تغییری رخ میدهد، یک React Element جدید تولید میشود. سپس React این شیء جدید را با نمونهی قبلی آن مقایسه کرده و تغییرات رخداده را محاسبه میکند. در آخر این تغییرات را به DOM واقعی اعمال میکند تا با Virtual DOM موجود هماهنگ شود.
بنابراین در حین کار با React، دیگر همانند کار با جاوا اسکریپت خالص و یا jQuery، مستقیما عناصر UI و DOM واقعی را تغییر نمیدهیم. در اینجا فقط state یک کامپوننت را تغییر میدهیم و سپس React، کار ایجاد شیء UI درون حافظهای متناظر با آن و سپس اعمال آنرا به UI نهایی قابل مشاهدهی در مرورگر، انجام میدهد. به همین جهت به این کتابخانه React میگویند! چون به تغییرات state کامپوننتها واکنش نشان میدهد و سپس DOM واقعی را به روز میکند.
Angular یا React؟!
هر دوی React و Angular از لحاظ طراحی کامپوننتها بسیار شبیه به هم هستند؛ اما Angular یک فریمورک است و React تنها یک کتابخانه. تنها کاری را که React انجام میدهد، رندر View است و هماهنگ نگه داشتن آن با state کامپوننتها. این تمام کاری است که React انجام میدهد؛ نه بیشتر و نه کمتر! بنابراین یادگیری React، بسیار سریعتر و سادهتر از Angular است. بدیهی است یک برنامهی تک صفحهای وب، از اجزای دیگری مانند مسیریابی و یا کار با سرویسهای HTTP نیز تشکیل میشود. در React شما مختار هستید که کتابخانههای جانبی فراهم شدهی برای آنرا خودتان انتخاب کرده و استفاده کنید؛ برخلاف روشی که در Angular مرسوم است و به صورت مشخص و ثابتی به همراه این فریمورک ارائه میشوند.
برپایی محیط توسعهی React
اولین برنامهای را که برای کار با React باید نصب کنید، node.js است. البته ما در این سری قرار نیست با node.js کار کنیم؛ اما از یکی از اجزای آن به نام node package manager یا npm، برای نصب کتابخانهی جاوا اسکریپتی ثالث، زیاد استفاده خواهیم کرد. پس از نصب آن، به خط فرمان مراجعه کرد و دستور زیر را صادر کنید:
> npm install -g npm@latest
اگر هم خیلی پیشترها node.js را نصب کردهاید (برای مثال چند سال قبل!)، نصب نگارش جدید آن احتمالا کار نخواهد کرد. حتی عزل و نصب مجدد آن نیز کارساز نیست. در این حالت باید پس از عزل آن، پوشههای قدیمی آنرا یکی یکی یافته و دستی حذف کنید . سپس مجددا آنرا نصب کنید.
در ادامه در خط فرمان و توسط npm، قالب create-react-app را نصب خواهیم کرد:
> npm i -g create-react-app
ابزار دیگری که در این سری از آن استفاده خواهیم کرد، ادیتور بسیار معروف و محبوب VSCode است. پس از دریافت و نصب آن، چند افزونهی زیر را نیز به آن اضافه خواهیم کرد:
برای نصب آنها، پنل extensions را در VSCode، از نوار ابزار کنار صفحهی آن، انتخاب کرده و نامهای فوق را در آن جستجو و سپس نصب کنید.
و یا میتوانید این فایل را اجرا کرده و تعدادی از افزونههای مفید VSCode را یکجا نصب کنید: install-addons.zip
همچنین قابلیت فرمتکردن پس از Save را نیز در VSCode فعال کنید تا پس از هربار Save، اعمال این افزونهها به صورت خودکار صورت گیرد. برای این منظور گزینهی file->preferences->settings را در VSCode انتخاب کرده و سپس save را جستجو کرده و Format On Save را انتخاب کنید:
علاوه بر اینها، جهت کار بهتر با VSCode، بهتر است بررسی کنندههای کدهای جاوا اسکریپتی (static code analyzers) را نیز با اجرای دستور زیر نصب کنید:
> npm i -g typescript eslint tslint eslint-plugin-react-hooks
پس از این تغییرات، نیاز است یکبار VSCode را بسته و مجددا باز کنید. سپس مجددا گزینهی file->preferences->settings را در VSCode انتخاب کرده و ابتدا eslint را در اینجا جستجو کنید. در صفحهی نمایش تنظیمات آن، گزینهی Auto fix on save آنرا انتخاب نمائید. در آخر در همین قسمت settings، عبارت prettier را انتخاب کنید. در اینجا اگر گزینهی قدیمی یکپارچگی با eslint آن هنوز وجود دارد، آنرا از حالت انتخاب شده خارج کنید (به صورت قرمز و deprecated نمایش داده میشود) تا افزونهی prettier بدون مشکل و خطا کار کند (disable Prettier ESLint integration).
ایجاد قالب اولین برنامهی React
در ادامه برای ایجاد اولین برنامهی React، از بستهی create-react-app که پیشتر آنرا نصب کردیم، استفاده میکنیم. برای این منظور در خط فرمان دستور زیر را صادر کنید:
> create-react-app sample-01
این قالب نه تنها React را نصب میکند، بلکه یک development server را برای اجرا و مشاهدهی سریع برنامه، webpack را برای یکی کردن فایلها (bundling & minification)، Babel را برای کامپایل کدهای فایلهای JSX و ... نیز نصب میکند. بنابراین به این ترتیب، یک پروژهی تنظیم شده و آمادهی استفاده و توسعه را شاهد خواهیم بود که نیازی به تنظیمات اولیهی آن نیست.
پس ایجاد برنامه، وارد پوشهی sample-01 شده و دستور npm start را صادر کنید:
> cd sample-01 > npm start
development server آن، تغییرات فایلهای برنامه را تحت نظر قرار میدهد و با هر تغییری، به صورت خودکار برنامه را در مرورگر بارگذاری مجدد خواهد کرد.
بررسی ساختار اولین پروژهی React ایجاد شده
ساختار پوشهها و فایلهای مثال اولیهی ایجاد شده توسط قالب create-react-app به صورت زیر است:
البته شما در این تصویر پوشهی node_modules را که در کنار این پوشهها قرار دارد، مشاهده نمیکنید. وجود یک چنین پوشهی سنگینی با هزاران فایل داخل آن، کار نمایشی IDEها را با مشکل مواجه میکند (مصرف حافظهی بالا، به همراه کند شدن شدید آن). اگر نمیخواهید این پوشه نمایش داده شود، در مسیر file->preferences->settings، عبارت npm را جستجو کرده و سپس در قسمت npm: exclude آن، بر روی لینک edit in settings.json کلیک کنید:
و سپس در فایل باز شده، یک چنین تنظیمی را میتوانید اضافه و یا ویرایش و تکمیل کنید:
"files.exclude": { "**/.git": true, "**/.svn": true, "**/.hg": true, "**/CVS": true, "**/.DS_Store": true, "**/node_modules": true, "**/wwwroot": true, "**/bower_components": true, "**/**/bin": true, "**/**/obj": true, "**/packages": true },
در ادامه پوشهی public این پروژه را مشاهده میکنید. تمام فایلهایی که قرار است به صورت عمومی توسط برنامه ارائه شوند، مانند favicon.ico و غیره، در این پوشه قرار میگیرند.
در این پوشه بر روی فایل index.html آن کلیک کنید تا بتوان محتوای آنرا بهتر بررسی کرد. برای مثال در ابتدای آن، درج تعدادی متادیتا را که یکی از آنها ذکر manifest.json است، مشاهده میکنید. کار فایل manifest.json، ارائهی یک سری متادیتای خاص مخصوص دستگاههای موبایل است که در آنها بجای favicon.ico، میتوان از تصاویر و یا آیکنهای بزرگتری مانند فایلهای png موجود در پوشهی public، استفاده کرد. در ادامهی این فایل، به تنظیم زیر میرسیم:
<body> <noscript>You need to enable JavaScript to run this app.</noscript> <div id="root"></div>
در پوشهی src و فایل App.js آن، شاهد یک کامپوننت ابتدایی هستید که کار رندر صفحهی مشکی پیشفرض این قالب را انجام میدهد. در این فایل، شاهد بازگشت یک چنین تگهایی هستیم:
return ( <div className="App"> <header className="App-header"> ... </header> </div> );
برای درک بهتر آن به آدرس https://babeljs.io/repl مراجعه کنید. سپس در سمت چپ صفحه، یک قطعه کد jsx را به یک ثابت انتساب دهید:
const element = <h1>Hello World!</h1>;
همانطور که مشاهده میکنید، این قطعه کد jsx (که یک رشتهی معمولی نیست)، توسط Babel به یک قطعه کد کاملا جاوا اسکریپتی قابل درک برای مرورگر تبدیل شدهاست:
"use strict"; var element = React.createElement("h1", null, "Hello World!");
بدیهی است نوشتن کدهای jsx، سادهتر از نوشتن قطعه کد فوق است و درک آن نیز به علت شباهت آن به HTML، آسانتر است. به همین جهت در کدهای React، ما از jsx استفاده میکنیم و تفسیر آنرا به Babel واگذار خواهیم کرد.
در پوشهی src، فایل مهم دیگری که وجود دارد، index.js است. این فایل نقطهی آغازین برنامه را مشخص میکند. در قسمتهای بعدی، محتویات این فایل را بیشتر بررسی خواهیم کرد.
در اینجا فایل serviceWorker.js را نیز مشاهده میکنید. این فایل به صورت خودکار توسط قالب create-react-app ایجاد شدهاست و کار آن کمک به ارائهی محلی برنامه، توسط development server آن است. بنابراین ما کاری با این فایل نخواهیم داشت.
نوشتن اولین برنامهی React
به پوشهی src ایجاد شده مراجعه کرده و تمام فایلهای موجود و پیشفرض آنرا حذف کنید. در ادامه خودمان آنها را از صفر ایجاد خواهیم کرد. برای این منظور فایل جدید و خالی src\index.js را ایجاد میکنیم. در ابتدای کار نیاز است تعدادی ماژول React را import کنیم.
import React from "react"; const element = <h1>Hello World!</h1>; console.log(element);
اگر هنوز برنامه توسط دستور npm start در حال اجرا است، هر بار که فایل index.js را ذخیره میکنیم، خروجی نهایی را در مرورگر نمایش میدهد (اگر هم آنرا بستهاید، یکبار از طریق خط فرمان، دستور npm start را در ریشهی پروژه، صادر کنید). به این قابلیت hot module reloading هم گفته میشود.
در این حالت اگر به مرورگر مراجعه کنید، یک صفحهی سفید را مشاهده خواهید کرد. اکنون دکمهی F12 را فشرده (و یا ctrl+shift+i) و developer console مرورگر را باز کنید.
شیءای را که در اینجا مشاهده میکنید، همان حاصل console.log کدهای فوق است؛ به عبارتی Babel، عبارت jsx ما را تبدیل به یک شیء جاوا اسکریپتی قابل فهم برای مرورگر کردهاست که از دیدگاه React، جزئی از همان Virtual DOM ای است که پیشتر معرفی شد (نمایش درون حافظهای DOM مختص React، جهت محاسبهی تغییرات، با تغییر state هر کامپوننت و سپس اعمال آنها به DOM اصلی در مرورگر).
اکنون میخواهیم این المان را در DOM اصلی، رندر کرده و نمایش دهیم:
import React from "react"; import ReactDOM from "react-dom"; const element = <h1>Hello World!</h1>; console.log(element); ReactDOM.render(element, document.getElementById("root"));
اکنون پس از ذخیره سازی فایل index.js، اگر به مرورگر مراجعه کنید، عبارت Hello World! را مشاهده خواهید کرد:
همانطور که در این تصویر نیز مشخص است، المان h1 ما را داخل div ای با id مساوی root، درج کردهاست.
هدف از این مثال ساده، نمایش نحوهی کارکرد React، در پشت صحنه بود. در یک برنامهی واقعی، بجای رندر یک المان ساده در DOM، کار رندر App component را انجام خواهیم داد. کامپوننت App، کامپوننت ریشهای برنامه بوده و میتواند شامل درختی از کامپوننتها که UI نهایی را تشکیل میدهند، شود.
نگاهی به تنظیمات پروژهی ایجاد شده
اگر فایل package.json پروژه را باز کنید، یک چنین بستههایی در آن درج شدهاست:
{ "name": "sample-01", "version": "0.1.0", "private": true, "dependencies": { "react": "^16.11.0", "react-dom": "^16.11.0", "react-scripts": "3.2.0" }, "scripts": { "start": "react-scripts start", "build": "react-scripts build", "test": "react-scripts test", "eject": "react-scripts eject" }, "eslintConfig": { "extends": "react-app" }, "browserslist": { "production": [ ">0.2%", "not dead", "not op_mini all" ], "development": [ "last 1 chrome version", "last 1 firefox version", "last 1 safari version" ] } }
بستهی react-scripts است که کار مدیریت چهار جزء قسمت scripts این فایل را انجام میدهد. برای نمونه دستور npm start ای که در اینجا تعریف شده، سبب اجرای react-scripts start میشود. در ادامه اگر دستور npm run build را اجرا کنیم، یک بستهی نهایی بهینه سازی شده را تولید میکند.
آخرین دستور آن eject است. اگر دستور npm run eject را اجرا کنید، امکان سفارشی سازی پشت صحنهی create-react-app را خواهید داشت؛ اما در نهایت به یک فایل package.json بسیار شلوغ خواهیم رسید (اینبار ارجاعات به Babel، Webpack و تمام ابزارهای دیگر نیز ظاهر میشوند). همچنین این عملیات نیز یک طرفهاست. یعنی از این پس قرار است کنترل تمام این پشت صحنه، در اختیار ما باشد و به روز رسانیهای بعدی create-react-app را با مشکل مواجه میکند. این گزینه صرفا مختص توسعه دهندگان پیشرفتهی React است.
کدهای کامل این قسمت را از اینجا میتوانید دریافت کنید: sample-01.zip
در قسمت بعد، پیشنیازهای جاوا اسکریپتی شروع به کار با React را بررسی میکنیم.
حتما تا به حال در وب سایتهای زیادی قسمت هایی را دیده اید که چیدمان عناصر آن به شکل زیر است:
این گونه چیدمان را حتما در منوی 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); });