مطالب
ASP.NET MVC #1

چرا ASP.NET MVC ؟

با وجود فریم ورک پخته‌ای به نام ASP.NET web forms، اولین سؤالی که حین سوئیچ به ASP.NET MVC مطرح می‌شود این است: «برای چی؟». بنابراین تا به این سؤال پاسخ داده نشود، هر نوع بحث فنی در این مورد بی فایده است.

مزایای ASP.NET MVC نسبت به ASP.NET web forms

1) سادگی نوشتن آزمون‌های واحد
مهم‌ترین دلیل استفاده از ASP.NET MVC صرفنظر از تمام دلایل دیگر، بحث طراحی ویژه آن جهت ساده سازی تهیه آزمون‌های واحد است. مشکل اصلی نوشتن آزمون‌های واحد برای برنامه‌های ASP.NET web forms، درگیر شدن مستقیم با تمام جزئیات طول عمر یک صفحه است. به علاوه فایل‌های code behind هر چند به ظاهر کدهای منطق یک صفحه را از کدهای HTML مانند آن جدا می‌کنند اما در عمل حاوی ارجاعات مستقیمی به تک تک عناصر بصری موجود در صفحه هستند (حس غلط جدا سازی کدها از اجزای یک فرم). اگر قرار باشد برای این وب فرم‌ها و صفحات، آزمون واحد بنویسیم باید علاوه بر شبیه سازی چرخه طول عمر صفحه و همچنین رخدادهای رسیده، کار وهله سازی تک تک عناصر بصری را نیز عهده دار شویم. اینجا است که ASP.NET web forms گزینه‌ی مطلوبی برای این منظور نخواهد بود و اگر نوشتن آزمون واحد برای آن غیرممکن نباشد، به همین دلایل آنچنان مرسوم هم نیست.
البته شاید بپرسید که این مساله چه اهمیتی دارد؟ امکان نوشتن ساده‌تر آزمون‌های واحد مساوی است با امکان ساده‌تر اعمال تغییرات به یک پروژه بزرگ. تغییرات در پروژه‌های بزرگی که آزمون واحد ندارند واقعا مشکل است. یک قسمت را تغییر می‌دهید، 10 قسمت دیگر به هم می‌ریزند. اینجا است که مدام باید به کارفرما گفت: «نه!»، «نمیشه!» یا به عبارتی «نمی‌تونم پروژه رو جمع کنم!» چون نمی‌تونم سریع برآورد کنم که این تغییرات کدام قسمت‌ها را تحت تاثیر قرار می‌دهند، کجا به هم ریخت. من باید خودم سریع بتونم مشخص کنم با این تغییر جدید چه قسمت‌هایی به هم ریخته تا اینکه دو روز بعد زنگ بزنند: «باز جایی رو تغییر دادی، یکجای دیگر کار نمی‌کنه!»

2) دستیابی به کنترل بیشتر بر روی اجزای فریم ورک
در طراحی ASP.NET MVC همه‌جا interface ها قابل مشاهد هستند. همین مساله به معنای افزونه پذیری اکثر قطعات تشکیل دهنده ASP.NET MVC است؛ برخلاف ASP.NET web forms. برای مثال تابحال چندین view engine، routing engine و غیره توسط برنامه نویس‌های مستقل برای ASP.NET MVC طراحی شده‌اند که هیچکدام با ASP.NET web forms میسر نیست. برای مثال از view engine پیش فرض آن خوشتان نمی‌آید؟ عوضش کنید! سیستم اعتبار سنجی توکار آن‌را دوست ندارید؟ آن‌را با یک نمونه بهتر تعویض کنید و الی آخر ...
به علاوه طراحی بر اساس interface ها یک مزیت دیگر را هم به همراه دارد و آن هم ساده سازی mocking (تقلید) آن‌ها است جهت ساده سازی نوشتن آزمون‌های واحد.

3) سرعت بیشتر اجرا
ASP.NET MVC یک سری از قابلیت‌های ذاتی ASP.NET web forms را مانند ViewState حذف کرده است. اگر وب را جستجو کنید، برنامه نویس‌های ASP.NET web forms مدام از این مساله شکایت دارند و راه‌ حل‌های مختلفی را جهت حذف یا فشرده سازی آن ارائه می‌دهند. ViewState در ابتدای امر جهت شبیه سازی محیط دسکتاپ در وب درنظر گرفته شده بود و مهاجرت ساده‌تر برنامه نویس‌های VB6 به وب، اما واقعیت این است که اگر یک برنامه نویس ASP.NET web forms به اندازه آن توجهی نداشته باشد، ممکن است حجم آن در یک صفحه پیچیده تا 500 کیلوبایت یا بیشتر هم برسد. همین مساله بر روی سرعت دریافت و اجرا تاثیر گذار خواهد بود.

4) کنترل‌های ASP.NET web forms آنچنان آش دهن‌سوزی هم نیستند!
خوب، ViewState حذف شده، بنابراین اکثر کنترل‌های ASP.NET web forms هم کاربرد آنچنانی در ASP.NET MVC نخواهند داشت؛ اما واقعیت این است که اکثر اوقات اگر شروع به سفارشی سازی یک کنترل توکار ASP.NET web forms کنید تا مطابق نیازهای کاری شما رفتار کند، پس از مدتی به یک کنترل کاملا از نو بازنویسی شده خواهید رسید! بنابراین در ابتدای امر تا 80 درصد کار اینطور به نظر می‌رسد که به عجب سرعت بالایی در توسعه دست یافته‌ایم، اما هنگامیکه قرار است این 20 درصد پایانی را پر کنیم، به این نتیجه خواهیم رسید که این کنترل‌ها با این وضع ابتدایی که دارند قابل استفاده نیستند و نیاز به دستکاری قابل ملاحظه‌ای دارند تا نیازهای واقعی کاری را برآورده کنند.

5) کنترل کامل بر روی HTML نهایی تولیدی
اگر علاقمند به کار با jQuery باشید، مدام نیاز خواهید تا با ID کنترل‌ها و عناصر صفحه کار کنید. پیشتر ASP.NET web forms این ID را یک طرفه و به صورت مقدار منحصربفردی تولید می‌کرد که جهت کار با فریم ورک‌های جاوا اسکریپتی عموما مشکل ساز بود. البته ASP.NET web forms در نگارش‌های جدید خود مشکل عدم امکان مقدار دهی ClientId سفارشی را برای کنترل‌های وب خود برطرف کرده است و این مورد را می‌توان دستی هم تنظیم کرد ولی در کل باز هم آنچنان کنترلی رو خروجی HTML نهایی کنترل‌های تولیدی نیست مگر اینکه مانند مورد چهارم یاد شده یک کنترل را از صفر بازنویسی کنید!
همچنین اگر باز هم بیشتر با jQuery و ASP.NET web forms کار کرده باشید می‌دانید که jQuery آنچنان سنخیتی با ViewState و Postback وب فرم‌ها ندارد و همین مساله عموما مشکل‌زا است. علاوه بر آن اخیرا مایکروسافت توسعه ASP.NET Ajax خود را تقریبا در حالت تعلیق و واگذار شده به شرکت‌های ثالث درآورده است و توصیه آن‌ها استفاده از jQuery Ajax است. اینجا است که مدل ASP.NET MVC سازگاری کاملی را با jQuery Ajax دارد هم از لحاظ نبود ViewState و هم از جنبه‌ی کنترل کامل بر روی markup نهایی تولیدی.
یا برای مثال خروجی پیش فرض یک GridView، جدول HTML ایی است که این روزها همه‌جا علیه آن صحبت می‌شود. البته یک سری آداپتور CSS friendly برای اکثر این کنترل‌ها موجود است و ... باز هم دستکاری بیش از حد کنترل‌های پیش فرض جهت رسیدن به خروجی دلخواه. تمام این‌ها را در ASP.NET MVC می‌شود با معادل‌های بسیار باکیفیت افزونه‌های jQuery جایگزین کرد و از همه مهم‌تر چون ViewState و مفاهیمی مانند PostBack حذف شده، استفاده از این افزونه‌ها مشکل ساز نخواهد بود.

6) استفاده از امکانات جدید زبان‌های دات نتی
طراحی اصلی ASP.NET web forms مربوط است به دوران دات نت یک؛ زمانیکه نه Generics وجود داشت، نه LINQ و نه آنچنان مباحث TDD یا استفاده از ORMs متداول بود. برای مثال شاید ایجاد یک strongly typed web form الان کمی دور از ذهن به نظر برسد، زمانیکه اصل آن بر مبنای بکارگیری گسترده datatable و dataset بوده است (با توجه به امکانات زبان‌های دات نتی در آن دوران). بنابراین اگر علاقمند هستید که این امکانات جدید را بکاربگیرید، ASP.NET MVC برای استفاده از آن‌ها طراحی شده است!

7) از ASP.NET web forms ساده‌تر است
طراحی ASP.NET MVC بر اساس ایده Convention over configuration است. به این معنا که اجزای آن بر اساس یک سری قرار داد در کنار هم مشغول به کار هستند. مشخص است View باید کجا باشد، نام کنترلرها چگونه باید تعیین شوند و قرار داد مرتبط به آن چیست، مدل باید کجا قرار گیرد، قرار داد پردازش آدرس‌های صفحات سایت به چه نحوی است و الی آخر. خلاصه در بدو امر با یک فریم ورک حساب شده که شما را در مورد نحوه استفاده صحیح از آن راهنمایی می‌کند، مواجه هستید.
به همین ترتیب هر پروژه MVC دیگری را هم که مشاهده کنید، سریع می‌توانید تشخیص دهد قراردادهای بکارگرفته شده در آن چیست. بنابراین اگر قرار است ASP.NET را امروز شروع کنید و هیچ سابقه‌ای هم از وب فرم‌ها ندارید، یک راست با ASP.NET MVC شروع کنید.

8) محدود به پیاده سازی مایکروسافت نیست
پیاده سازی‌های مستقلی هم از ASP.NET MVC توسط اشخاص و گروه‌های خارج از مایکروسافت وجود دارد: ^، ^، ^، ^ و ...


و در پایان یکی دیگر از دلایل سوئیچ به ASP.NET MVC ، «یاد گرفتن یک چیز جدید است» یا به عبارتی فرا گرفتن یک روش دیگر برای حل مسایل، هیچگاه ضرری را به همراه نخواهد داشت که هیچ، بلکه باعث بازتر شدن میدان دید نیز خواهد گردید.


یک دیدگاه دیگر
ASP.NET MVC برای شما مناسب نخواهد بود اگر ...
1) با پلی‌مرفیزم مشکل دارید.
ASP.NET MVC پر است از interfaces، abstract classes، virtual methods و امثال آن. بنابراین اگر تازه کار هستید، ابتدا باید مفاهیم شیءگرایی را تکمیل کنید.

2) اگر نمی‌توانید فریم ورک خودتون رو بر پایه ASP.NET MVC بنا کنید!
ASP.NET MVC برخلاف وب فرم‌ها به همراه آنچنان تعداد بالایی کنترل و افزونه از پیش مهیا شده نیست. در بدو امر شما فقط یک سری url helper، html helper و ajax helper ساده را خواهید دید؛ این نقطه ضعف ASP.NET MVC نیست. عمدا به این نحو طراحی شده است. همانطور که عنوان شد اکثر اجزای این فریم ورک قابل تعویض است. بنابراین دست شما را باز گذاشته است تا با پیاده سازی این اینترفیس‌ها، امکانات جدیدی را خلق کنید. البته پس از این چندین و چند سال که از ارائه آن می‌گذرد، به اندازه کافی افزونه برای ASP.NET MVC طراحی شده است که به هیچ عنوان احساس کمبود نکنید یا اینکه نیازی هم نداشته باشید تا آنچنان فریم ورک خاصی را بر پایه ASP.NET MVC تهیه کنید. برای مثال پروژه MvcContrib موجود است یا شرکت telerik یک مجموعه سورس باز کامل مخصوص ASP.NET MVC را ارائه داده است و الی آخر.

3) اگر نمی‌توانید از کتابخانه‌های سورس باز استفاده کنید.
همانطور که عنوان شد ASP.NET MVC به همراه کوهی از کنترل‌ها ارائه نشده است. اکثر افزونه‌های آن سورس باز هستند و کار با آن‌ها هم دنیای خاص خودش را دارد. چگونه باید کتابخانه‌های مناسب را پیدا کرد، کجا سؤال پرسید، کجا باگ گزارش داد، چگونه مشارکت کرد و غیره. خلاصه منتظر یک بسته شکیل حاضر و آماده نباید بود. خود ASP.NET MVC هم تحت مجوز MSPL به صورت سورس باز در دسترس است.


و یک نکته تکمیلی
مایکروسافت مدتی است شروع کرده به پرورش و زمزمه ایده «یک ASP.NET واحد». به عبارتی قصد دارند در یکی دو نگارش بعد، این دو (وب فرم و MVC) را یکی کنند. هم اکنون اگر مطالب وبلاگ‌ها را مطالعه کنید زیرساخت آن به نام ASP.NET Web API آماده شده است و در مرحله بتا است. نکته جالب اینجا است که این Web API امکان تعریف یکپارچه و مستقیم کنترلر‌های MVC را در وب فرم‌ها میسر می‌کند. ولی باز هم نام آن Controller است یعنی جزئی از ASP.NET MVC و کسی می‌تواند از آن استفاده کند که با MVC‌ مشکلی نداشته باشد. بنابراین یادگیری MVC هیچ ضرری نخواهد داشت و جای دوری نخواهد رفت!



مطالب
Gulp #3
در قسمت اول گالپ را معرفی کردیم و در مقاله قبلی به نوشتن اولین تسک با گالپ پرداختیم. در این قسمت می‌خواهیم با نصب bower، پروژه‌ی workflow بوت استرپ راستچین شده را انجام دهیم.

نصب bower



bower یک مدیریت پکیج سمت Front end است و از مزیای استفاده از آن می‌توان به موارد زیر اشاره کرد:
  • ساده کردن تعریف وابستگی‌های منابع پروژه با تعریف یک فایل bower.json
  • نیازی به commit کردن واستگی‌های پروژه نیست.
  • با ذکر ورژن مربوط به وابستگی یا محدوده‌ی قابل قبول برای آن، به روز رسانی منابع به سادگی با یک دستور انجام می‌شود.
  • وابستگی های وابسته به یک منبع  را نیز نصب می‌کند. برای مثال زمانیکه بوت استرپ را به عنوان وابستگی پروژه تعریف می‌کنیم، وابستگی آن یعنی jquery را چون در فایل bower.json بوت استرپ تعریف شده‌است، به صورت خودکار دانلود می‌کند.
  • در نهایت افراد هم تیمی یا توسعه دهندگان دیگر به راحتی با زدن دستور bower install تمام وابستگی‌های پروژه را می‌توانند نصب کنند.
برای نصب آن کافی است دستور زیر را بزنید و بعد از نصب نیز دستور خط دوم را در مسیر پروژه وارد کنید تا یک فایل bower.json را برایمان بسازد. برای اینکار به سوال‌هایی که می‌پرسد باید جواب دهیم. تنها نکته‌ای که قابل ذکر است، پاسخ به سوال ? what types of modules does this package expose است که باید گزینه‌ی Node را انتخاب کنید. 
sudo npm install -g bower
bower init
حال می‌خواهیم وابستگی‌های پروژه را نصب کنیم که عبارتند از bootstrap-sass,fontawesome,bootstrap-rtl :
bower install bootstrap-sass-official --save  
bower install fontawesome --save
bower install bootstrap-rtl --save
نکته : عبارت save-- وابستگی مربوطه را به bower.json اضافه می‌کند. اگر نصب با موفقیت صورت گرفته باشد، پکیج‌های مربوطه را می‌توانید در فولدر bower_components در root پروژه مشاهده کنید.

نصب پلاگین‌های مورد نیاز gulp

ما می‌خواهیم که بوت استرپ و نگارش sass آن‌را کامپایل کنیم و همچنین وابستگی‌های bower پروژه را از طریق گالپ نصب کنیم تا نیازی به زدن bower install نباشد و توسعه دهنده‌ی پروژه فقط با زدن npm install، تمام وابستگی‌های پروژه‌ی ما را نصب کند. می‌توان تمام پلاگین‌ها را پشت سر هم با یک دستور نصب کرد و یا به صورت جداگانه این کار را انجام داد.
sudo npm install gulp gulp-ruby-sass gulp-notify gulp-bower --save-dev
نکته۱:پلاگین gulp-notify به منظور نشان دادن خطاها در ترمینال است؛ تا در صورت وجود اشتباه در کامپایل فایل‌های Sass، کل روند گالپ متوقف نشود.
نکته ۲ : برای اینکه کامپایل sass انجام شود نیاز به Ruby دارید. برای ویندوز می‌توانید از روبی اینستالر استفاده کنید.

نوشتن تسک‌ها برای گالپ

به قسمت مهم و هیجان انگیز کار رسیدیم! همان طور در مقاله قبلی گفتیم، ابتدا باید ماژول‌هایی را که نصب کردیم، include کنیم به این صورت:
var gulp = require('gulp'),
    sass = require('gulp-ruby-sass'),
    notify = require('gulp-notify'),
    bower = require('gulp-bower');
برای اینکه دسترسی به مسیر‌های مهم پروژه آسان‌تر شود، آن‌ها را درون یک شیء نگه داری می‌کنیم.
var config = {
    sassPath = './resources/sass',
    bowerDir = './bower_components'
}
تسک باور را اضافه می‌کنیم تا کار bower install را خودکار کنیم. مزیت این کار این است اگر یک هم تیمی، پکیج جدیدی را در حین توسعه‌ی پروژه نصب کرد، بدون اینکه لازم باشد تا در جایی از پروژه، بقیه را از آن مطلع کنید، فقط با زدن gulp خیالتان راحت شود که تمام کارهایی که باید انجام دهید، گالپ برایتان انجام می‌دهد.
// create a task to do bower install
gulp.task('bower', function() {
    return bower()
      .pipe(gulp.dest(config.bowerDir))
});
در گام بعدی، تسک جاوا اسکریپت را اضافه می‌کنیم. یعنی جی کوری و فایل bootstrap.js را به مسیر public/js می‌آوریم. فولدر public برای جدا سازی فایل‌های نهایی از فایل‌های توسعه است و به همین صورت برای فونت آیکن‌های fontawesome.

// Copy js files to public folder
gulp.task('js', function() {
    return gulp.src([config.bowerDir + '/bootstrap-sass-official/assets/javascripts/bootstrap.min.js',
                    config.bowerDir + '/jquery/dist/jquery.min.js'
            ])
        .pipe(gulp.dest('./public/js'));

});

// Copy fontawesome icons to public/fonts folder
gulp.task('icons', function() {
    return gulp.src(config.bowerDir + '/fontawesome/fonts/**.*')
        .pipe(gulp.dest('./public/fonts'));
});

:برای سی اس اس هم تسک مربوطه‌اش را به صورت زیر می‌نویسیم

gulp.task('css', function() {
    return sass(config.sassPath + '/style.scss', { // Our coustom sass
      style: 'compressed',  // minify css
      loadPath: [ // load paths to easy use import in resources/sass
        './resources/sass',
        config.bowerDir + '/bootstrap-sass-official/assets/stylesheets', // bootstrap sass files
        config.bowerDir + '/fontawesome/scss' // awesome icons sass files
      ]
    })
});
 حال تعریف می‌کنیم که اگر خطایی در حین کامپایل رخ داد، آن را به ما نشان دهد و در نهایت فایل کامپایل و فشرده شده را در مسیر خروجی قرار می‌دهیم. کد‌ها را به صورت زیر به روز می‌کنیم
gulp.task('css', function() {
    return sass(config.sassPath + '/style.scss', { // Our coustom sass
      style: 'compressed',  // minify css
      loadPath: [ // load paths to easy use import in resources/sass
        './resources/sass',
        config.bowerDir + '/bootstrap-sass-official/assets/stylesheets', // bootstrap sass files
        config.bowerDir + '/fontawesome/scss' // awesome icons sass files
      ]
    })
        .on('error', notify.onError(function(error) {
            return 'Error: ' + error.message;
        }))
      .pipe(gulp.dest('./public/css'));
});
اینجا ما می‌خواهیم که کار‌ها را خودکار سازی کنیم تا با تغییر و ذخیره‌ی مجدد فایل‌های سس، تسک سی اس اس را انجام دهد. برای این کار کد‌های زیر را اضافه می‌کنیم
// Rerun the task when a file changes
gulp.task('watch', function() {

    gulp.watch(config.sassPath + '/**/*.scss', ['css']); 

});
در نهایت برای سادگی می‌توانیم مجموعه‌ای از وظایف را در یک تسک تعریف کنیم تا به راحتی و با زدن تنها یک دستور در ترمینال، کار‌ها خودکار سازی شوند
// Run this task with :  gulp
// OR gulp default
gulp.task('default', ['bower', 'icons', 'css','js']);

بسیار خوب؛ ما توانستیم پایه‌ی ورک فلو یمان را بسازیم. در مقاله‌ی بعدی از پلاگین‌های دیگری برای بهینه سازی کارهایمان کمک خواهیم گرفت

مخزن گیت هاب 
مطالب
React 16x - قسمت 15 - مسیریابی - بخش 1 - تعریف و تنظیم مسیریابی‌ها
React برخلاف Angular، دارای قابلیت‌های توکار مسیریابی نیست و تنها کاری را که انجام می‌دهد همان رندر View است که تا اینجا بررسی کردیم. به همین جهت در این قسمت ابتدا یک برنامه‌ی عمومی و ساده را با نصب کتابخانه‌ی ثالثی برای توضیح مفاهیم مسیریابی، شامل کار با پارامترهای مسیریابی، کوئری استرینگ‌ها، هدایت کاربران به صفحات دیگر، مدیریت صفحات یافت نشده و مسیریابی تو در تو، بررسی می‌کنیم. سپس به عنوان تمرین، همان برنامه‌ی طراحی گریدی را که تا قسمت 14 تکمیل کردیم، با معرفی مسیریابی بهبود خواهیم بخشید.


برپایی پیش‌نیازها

در اینجا برای بررسی مسیریابی، یک پروژه‌ی جدید React را ایجاد می‌کنیم.
> create-react-app sample-15
> cd sample-15
> npm start
در ادامه توئیتر بوت استرپ 4 را نیز نصب می‌کنیم. برای این منظور پس از باز کردن پوشه‌ی اصلی برنامه توسط VSCode، دکمه‌های ctrl+` را فشرده (ctrl+back-tick) و دستور زیر را در ترمینال ظاهر شده وارد کنید:
> npm install --save bootstrap
سپس برای افزودن فایل bootstrap.css به پروژه‌ی React خود، ابتدای فایل index.js را به نحو زیر ویرایش خواهیم کرد:
import "bootstrap/dist/css/bootstrap.css";
این import به صورت خودکار توسط webpack ای که در پشت صحنه کار bundling & minification برنامه را انجام می‌دهد، مورد استفاده قرار می‌گیرد.

همچنین کتابخانه‌ی ثالث بسیار معروف react-router-dom را نیز نصب می‌کنیم:
> npm i react-router-dom --save
نگارش dom آن مخصوص کار با مرورگر است و نگارش native آن (react-router-native)، مخصوص React Native و تولید برنامه‌های موبایل می‌باشد که مبحث دیگری است.


افزودن مسیریابی به برنامه

پس از نصب کتابخانه‌ی react-router-dom، برای افزودن آن به برنامه و فعالسازی مسیریابی، به فایل index.js مراجعه کرده و import آن‌را به ابتدای فایل اضافه می‌کنیم:
import { BrowserRouter } from "react-router-dom";
سپس کامپوننت App را داخل BrowserRouter، محصور می‌کنیم:
ReactDOM.render(
  <BrowserRouter>
    <App />
  </BrowserRouter>,
  document.getElementById("root")
);
کار BrowserRouter، محصور سازی مدیریت تاریخچه‌ی مرور صفحات در مرورگر و انتقال آن به درخت کامپوننت‌های React است. به این ترتیب در هر قسمتی از درخت کامپوننت‌های برنامه می‌توان از History object مرورگر استفاده کرد.


ثبت و معرفی مسیریابی‌ها

در ادامه باید مسیریابی‌های خود را ثبت کنیم؛ به این معنا که بر اساس URL خاصی، چه کامپوننتی باید رندر شود. به همین جهت پوشه‌ی جدید src\components را ایجاد کرده و کامپوننت src\components\navbar.jsx را که یک کامپوننت تابعی بدون حالت است، در آن تعریف می‌کنیم:
import React from "react";

const NavBar = () => {
  return (
    <nav className="navbar bg-dark navbar-dark navbar-expand-sm">
      <div className="navbar-nav">
        <a className="nav-item nav-link" href="/">
          Home
        </a>
        <a className="nav-item nav-link" href="/products">
          Products
        </a>
        <a className="nav-item nav-link" href="/posts/2018/06">
          Posts
        </a>
        <a className="nav-item nav-link" href="/admin">
          Admin
        </a>
      </div>
    </nav>
  );
};

export default NavBar;
کار آن نمایش منوی بالای صفحه است.


سپس به فایل app.js مراجعه کرده و ساختار آن‌را به صورت زیر، جهت درج این NavBar، ویرایش می‌کنیم تا سبب رندر و نمایش منوی راهبری در مرورگر شود:
import "./App.css";

import React, { Component } from "react";

import NavBar from "./components/navbar";

class App extends Component {
  render() {
    return (
      <div>
        <NavBar />
      </div>
    );
  }
}

export default App;
در ادامه در کامپوننت App، یک container را اضافه می‌کنیم که قرار است در آن بر اساس URL رسیده، محتوای کامپوننت خاصی رندر شود. به همین جهت کامپوننت Route را در اینجا قرار می‌دهیم و در آن یک یا چند Route را ثبت می‌کنیم:
import "./App.css";

import React, { Component } from "react";
import { Route } from "react-router-dom";

import Dashboard from "./components/admin/dashboard";
import Home from "./components/home";
import NavBar from "./components/navbar";
import Posts from "./components/posts";
import Products from "./components/products";

class App extends Component {
  render() {
    return (
      <div>
        <NavBar />
        <div className="container">
          <Route path="/products" component={Products} />
          <Route path="/posts" component={Posts} />
          <Route path="/admin" component={Dashboard} />
          <Route path="/" component={Home} />
        </div>
      </div>
    );
  }
}

export default App;
Route نیز یک کامپوننت است؛ همانند تمام کامپوننت‌هایی که تاکنون تعریف کردیم و دارای چند ویژگی است که به صورت props به آن منتقل می‌شوند. برای نمونه خاصیت path آن به مسیر products/ در مرورگر اشاره می‌کند و سبب رندر کامپوننت جدید Products که در بالای این ماژول نیز import شده، می‌شود. در اینجا سه مسیریابی دیگر را نیز ثبت کرده‌ایم که کامپوننت‌های جدید متناظر با آن‌ها به صورت زیر تعریف می‌شوند:

کامپوننت جدید src\components\products.jsx جهت رندر لیست آرایه‌ی اشیاء product:
import React, { Component } from "react";

class Products extends Component {
  state = {
    products: [
      { id: 1, name: "Product 1" },
      { id: 2, name: "Product 2" },
      { id: 3, name: "Product 3" }
    ]
  };

  render() {
    return (
      <div>
        <h1>Products</h1>
        <ul>
          {this.state.products.map(product => (
            <li key={product.id}>
              <a href={`/products/${product.id}`}>{product.name}</a>
            </li>
          ))}
        </ul>
      </div>
    );
  }
}

export default Products;

کامپوننت بدون حالت تابعی src\components\home.jsx با این محتوا:
import React from "react";

const Home = () => {
  return <h1>Home</h1>;
};

export default Home;

کامپوننت بدون حالت تابعی src\components\posts.jsx با این محتوا:
import React from "react";

const Posts = () => {
  return (
    <div>
      <h1>Posts</h1>
      Year: , Month:
    </div>
  );
};

export default Posts;

کامپوننت بدون حالت تابعی src\components\admin\dashboard.jsx در پوشه‌ی جدید admin با این محتوا:
import React from "react";

const Dashboard = ({ match }) => {
  return (
    <div>
      <h1>Admin Dashboard</h1>
    </div>
  );
};

export default Dashboard;
تا اینجا اگر برنامه را اجرا کنیم، در اولین بار نمایش آن، شاهد رندر کامپوننت Home خواهیم بود. اما اگر در همین حالت بر روی لیست products، در منوی بالای صفحه کلیک کنیم، هم کامپوننت products و هم کامپونت home، هر دو با هم رندر شده‌اند. یک چنین رفتاری را در سایر صفحات نیز می‌توان مشاهده کرد:



معرفی کامپوننت Switch

<div className="container">
  <Route path="/products" component={Products} />
  <Route path="/posts" component={Posts} />
  <Route path="/admin" component={Dashboard} />
  <Route path="/" component={Home} />
</div>
الگوریتم تطابق کامپوننت Route، ابتدا بررسی می‌کند که آیا برای مثال URL ای با path مساوی products/ شروع شده‌است؟ اگر اینطور است، کامپوننت متناظر با آن را که برای نمونه در اینجا Products است، رندر می‌کند. این حالت جهت مسیری مانند products/new/ نیز صدق می‌کند؛ چون این URL نیز با products/ شروع شده‌است. همچنین این تطابق‌گر، مسیر ثبت شده‌ی برای کامپوننت Home را نیز چون با / شروع شده‌است و جزء ابتدایی مسیر products/ است هم رندر می‌کند. به همین جهت است که وقتی مسیر products/ را درخواست می‌دهیم، در صفحه دو کامپوننت products و home، با هم رندر می‌شوند.
یک روش حل این مشکل، استفاده از ویژگی exact است:
<Route path="/" exact component={Home} />
به این ترتیب اگر مسیر درخواستی دقیقا مساوی / بود، کامپوننت Home را رندر خواهد کرد. با این تغییر، با مراجعه‌ی به آدرس products/، دیگر رندر کامپوننت home را شاهد نخواهیم بود:


راه دوم رفع این مشکل، استفاده از کامپوننت Switch است. به همین جهت ابتدا این کامپوننت را import می‌کنیم:
import { Route, Switch } from "react-router-dom";
سپس تمام Routeهای تعریف شده را داخل Switch محصور خواهیم کرد:
class App extends Component {
  render() {
    return (
      <div>
        <NavBar />
        <div className="container">
          <Switch>
            <Route path="/products" component={Products} />
            <Route path="/posts" component={Posts} />
            <Route path="/admin" component={Dashboard} />
            <Route path="/"  component={Home} />
          </Switch>
        </div>
      </div>
    );
  }
}
Switch اولین مسیریابی را که با URL داده شده تطابق داشته باشد، رندر می‌کند. همچنین در اینجا دیگر نیازی به ذکر ویژگی exact نیز وجود ندارد. بنابراین با استفاده از Switch اگر مسیر داده شده، products/ باشد، مسیریابی تعریف شده‌ی با آن یافت می‌شود که در اینجا اولین Route تعریف شده‌است. سپس کار رندر کامپوننت آن‌را انجام داده و از مابقی مسیریابی‌های تعریف شده، صرفنظر می‌کند.
بنابراین هنگام کار با Switch، ترتیب مسیریابی‌های تعریف شده مهم است و باید از یک مسیریابی ویژه شروع شده و به یک مسیریابی عمومی مانند / ختم شود.


معرفی کامپوننت Link

تا اینجا اگر برنامه را اجرا کرده باشید و پیشتر سابقه‌ی کار با برنامه‌های SPA یا Single page applications را داشته باشید، یک مشکل دیگر را نیز احساس کرده‌اید: سیستم مسیریابی که تا کنون تعریف کرده‌ایم، به صورت SPA عمل نمی‌کند. یعنی به ازای هربار کلیک بر روی لینک‌های منوی راهبری سایت، یکبار دیگر به طور کامل برنامه از صفر بارگذاری مجدد می‌شود و تمام اسکریپت‌های آن مجددا از سرور دریافت شده و رندر خواهند شد. این مورد را در برگه‌ی network ابزارهای توسعه دهندگان مرورگر خود بهتر می‌توانید مشاهده کنید. به ازای هر درخواست نمایش کامپوننتی، تعدادی درخواست HTTP به سمت سرور ارسال می‌شوند که برای دریافت صفحه‌ی index و bundle.js برنامه هستند. اما در برنامه‌های SPA، مانند جمیل، با هربار کلیک بر روی لینکی، شاهد ریفرش و بارگذاری مجدد کل آن صفحه نیستیم و تنها اطلاعات موجود در قسمت container به روز می‌شوند.

یک نکته: در اینجا ممکن است دو درخواست websocket و info را نیز مشاهده کنید. این دو مرتبط به hot module reloading هستند که با ذخیره‌ی برنامه در ادیتور VSCode، بلافاصله سبب به روز رسانی و ریفرش برنامه در مرورگر می‌شوند.

برای رفع مشکل SPA نبودن برنامه، باید به کامپوننت NavBar مراجعه کرده و تمام anchor‌های استاندارد تعریف شده‌ی در آن‌را با کامپوننت Link جایگزین کنیم:
import React from "react";
import { Link } from "react-router-dom";

const NavBar = () => {
  return (
    <nav className="navbar bg-dark navbar-dark navbar-expand-sm">
      <div className="navbar-nav">
        <Link className="nav-item nav-link" to="/">
          Home
        </Link>
        <Link className="nav-item nav-link" to="/products">
          Products
        </Link>
        <Link className="nav-item nav-link" to="/posts/2018/06">
          Posts
        </Link>
        <Link className="nav-item nav-link" to="/admin">
          Admin
        </Link>
      </div>
    </nav>
  );
};

export default NavBar;
در اینجا ابتدا کامپوننت Link را در ابتدای ماژول، import کردیم. سپس تمام anchorها را یافته و تبدیل به کامپوننت Link نمودیم. همچنین href آن‌ها را نیز به ویژگی to تغییر دادیم.
با این تغییرات اگر برنامه را اجرا کنیم، اینبار با کلیک بر روی هر لینک، دیگر شاهد بارگذاری کامل صفحه در مرورگر نخواهیم بود؛ بلکه تنها قسمت container ای که کامپوننت Route مسیریابی در آن درج شده‌است، به روز رسانی می‌شود و این عملیات نیز بسیار سریع است؛ از این جهت که محتوای این کامپوننت‌ها از همان bundle.js حاوی تمام کدهای برنامه تامین می‌شود و این فایل تنها یکبار در آغاز برنامه از سرور خوانده شده و سپس توسط مرورگر پردازش می‌شود. بنابراین در برنامه‌های SPA، برخلاف برنامه‌های وب معمولی، هربار که کاربر آدرس متفاوتی را انتخاب می‌کند، بارگذاری مجدد برنامه و خوانده شدن محتوای متناظر از سرور صورت نمی‌گیرد؛ این محتوا هم اکنون در bundle.js برنامه مهیا است و قابلیت استفاده‌ی آنی را دارد.

اما کامپوننت Link چگونه کار می‌کند؟
کامپوننت لینک در نهایت همان anchor‌های استاندارد را رندر می‌کند؛ اما به هر کدام یک onClick را نیز اضافه می‌کند که سبب جلوگیری از رفتار پیش‌فرض anchor می‌شود. به همین جهت مرورگر درخواست اضافه‌ای را به سمت سرور ارسال نمی‌کند. در اینجا مدیریت کننده‌ی onClick، تنها Url بالای صفحه را در مرورگر تغییر می‌دهد. اکنون که Url تغییر کرده‌است، یکی از مسیریابی‌های تعریف شده، با این Url تطابق یافته و سپس کامپوننت متناظر با آن‌را رندر می‌کند.


بررسی Route props


اگر بر روی لینک نمایش products در منوی راهبری سایت کلیک کرده و سپس به خروجی افزونه‌ی react developer tools دقت کنیم (تصویر فوق)، مشاهده می‌کنیم که این کامپوننت هم اکنون تعدادی خاصیت را به صورت props در اختیار دارد؛ مانند history (امکان هدایت کاربر را به صفحه‌ای دیگر دارد)، location (آدرس جاری برنامه) و match (اطلاعاتی در مورد الگوریتم تطابق مسیر). کار تنظیم این props، توسط کامپوننت Route ای که کار ثبت مسیریابی‌ها را انجام می‌دهد، صورت می‌گیرد. به عبارتی کامپوننت Route، محصور کننده‌ی کامپوننتی است که آن‌را به عنوان پارامتر، دریافت و در صورت تطابق با مسیر جاری، آن‌را رندر می‌کند. همچنین در این بین کار تزریق خواص props یاد شده را نیز انجام می‌دهد.


ارسال props سفارشی در حین مسیریابی به کامپوننت‌ها

همانطور که بررسی کردیم، کامپوننت Route، حداقل سه خاصیت props را به کامپوننت‌هایی که رندر می‌کند، تزریق خواهد کرد. اما در اینجا برای تزریق خواص سفارشی چگونه باید عمل کرد؟
در حین کار با کامپوننت Route، برای ارسال props اضافی، بجای استفاده از ویژگی component آن، باید از ویژگی render استفاده کرد:
<Route
  path="/products"
  render={() => <Products param1="123" param2="456" />}
/>
در اینجا کار با تعریف یک arrow function شروع می‌شود که در نهایت المان کامپوننت مدنظر را همانند روش متداولی که برای تعریف تمام کامپوننت‌های React و تنظیم ویژگی‌های آن‌ها استفاده می‌شود، بازگشت می‌دهد که تاثیر آن‌را در خروجی افزونه‌ی react developer tools بهتر می‌توان مشاهده کرد:


البته اگر به تصویر فوق دقت کنید، سایر خواص پیشینی که تزریق شده بودند مانند history، location و match، دیگر در اینجا حضور ندارند. برای رفع این مشکل باید تعریف arrow function انجام شده را به صورت زیر تغییر داد:
<Route
  path="/products"
  render={props => (
    <Products param1="123" param2="456" {...props} />
  )}
/>
ابتدا پارامتر arrow function را به همان props تنظیم می‌کنیم. سپس با استفاده از spread operator، این props را در المان JSX تعریف شده، گسترده و تزریق می‌کنیم؛ با این خروجی:



کدهای کامل این قسمت را از اینجا می‌توانید دریافت کنید: sample-15-part-01.zip
مطالب
مسیریابی تو در تو در Angularjs با استفاده از UI-Router

UI-Router   ابزاری برای مسیریابی در AngularJS است که این امکان را برایتان فراهم می‌کند تا بخش‌های برنامه رابط کاربریتان را به شکل یک ماشین حالت ساماندهی کنید. برخلاف سرویس route$ که بر اساس مسیریابی URL‌ها ساماندهی شده و کار می‌کند، UI-Router بر اساس حالت‌ها کار می‌کند، که این حالت‌ها می‌توانند در صورت لزوم مسیریابی هم داشته باشند.


UI-Router یکی از افزونه‌های مجموعه Angular-ui، و پاراگراف بالا معرفی آن در صفحه خانگیش است (تقریبا!). این افزونه جزئیات مفصلی دارد و در این مطلب تنها به معرفی آن خواهم پرداخت (بر اساس مطالب صفحه خانگیش). پیش از ادامه پیشنهاد می‌کنم اگر مطالب زیر را نخوانده‌اید ابتدا آن‌ها را مرور کنید:
برای استفاده از UI-Router باید:
  1. فایل جاوا اسکریپت آن را دانلود کنید (released یا minified).
  2. در صفحه اصلی برنامه‌تان پس از include کردن فایل اصلی AngularJS فایل angular-ui-router.js (یا angular-ui-router.min.js) را include کنید.
  3. 'ui.router' را به لیست وابستگی‌های ماژول اصلی اضافه کنید.
نتیجه چیزی شبیه این خواهد بود:
<!doctype html>
<html ng-app="myApp">
<head>
    <script src="//ajax.googleapis.com/ajax/libs/angularjs/1.1.5/angular.min.js"></script>
    <script src="js/angular-ui-router.min.js"></script>
    <script>
        var myApp = angular.module('myApp', ['ui.router']);
        // For Component users, it should look like this:
        // var myApp = angular.module('myApp', [require('angular-ui-router')]);
    </script>
    ...
</head>
<body>
    ...
</body>
</html>

حالت‌ها و viewهای تو در تو

قابلیت اصلی UI-Router امکان تعریف حالت‌ها و vieweهای تو در تو است. در مطلب مسیریابی در AngularJs #بخش اول دایرکتیو ng-view معرفی شده است. هنگام استفاده از سرویس route$ با این دایرکتیو می‌توان محل مورد نظر برای بارگذاری محتویات مربوط به مسیرها را مشخص کرد. دایرکتیو ui-view در UI-Router همین نقش را دارد. فرض کنید این کد فایل index.html باشد:
<!-- index.html -->
<body>
    <div ui-view></div>
    <!-- We'll also add some navigation: -->
    <a ui-sref="state1">State 1</a>
    <a ui-sref="state2">State 2</a>
</body>
همانطور که ملاحظه می‌کنید در تگ‌های a از دایرکتیو ui-sref استفاده شده است. این دایرکتیو علاوه بر مدیریت تغییر حالت، خصوصیت href تگ a را در صورتی که حالت مشخص شده URL داشته باشد تولید می‌کند. البته برای استفاده از UI-Router ملزم به استفاده از دایرکتیو ui-sref نیستید و می‌توانید href را مشخص کنید. ولی با استفاده از ui-sref لازم نیست مسیر یک حالت را به یاد داشته باشید، و یا در صورت تغییر آن، همه hrefها را به روز کنید.
در ادامه برای هر کدام از حالت‌ها یک template اضافه می‌کنیم:
فایل state1.html:
<!-- partials/state1.html -->
<h1>State 1</h1>
<hr/>
<a ui-sref="state1.list">Show List</a>
<div ui-view></div>
فایل state2.html:
 <!-- partials/state2.html -->
<h1>State 2</h1> 
<hr /> 
<a ui-sref="state2.list">Show List</a> 
<div ui-view></div>

دو نکته قابل توجه در این templateها وجود دارد. اول اینکه همانطور که می‌بینید templateها خود شامل تگی با دایرکتیو ui-view هستند. و دوم مقدار دایرکتیو ui-sref است که به صورت state1.list و state2.list آمده است. این جدا سازی با نقطه نشان دهنده سلسله مراتب حالت‌هاست. یعنی حالت‌های state1 و state2 هرکدام حالت فرزندی به نام list دارند. در ادامه وقتی حالت‌ها و مسیریابی را در ()app.config تعریف کنیم این مسائل از هاله‌ای از ابهام که در آن هستند خارج می‌شوند! فعلا بیاید با راهنمای UI-Router پیش برویم و فایل‌های template حالت‌های فرزند را تعریف کنیم. templateهایی که قرار است در ui-view پدرانشان بارگذاری شوند:
<!-- partials/state1.list.html -->
<h3>List of State 1 Items</h3>
<ul>
  <li ng-repeat="item in items">{{ item }}</li>
</ul>

<!-- partials/state2.list.html -->
<h3>List of State 2 Things</h3>
<ul>
  <li ng-repeat="thing in things">{{ thing }}</li>
</ul>
خوب! حالا برویم سراغ شعبده بازی! برای اینکه از UI-Router استفاده کنید لازم است stateProvider$ و urlRouterProvider$ را به عنوان وابستگی به ()app.config تزریق کنید:
myApp.config(['$stateProvider', '$urlRouterProvider',
function($stateProvider, $urlRouterProvider) {
  //
  // For any unmatched url, redirect to /state1
  $urlRouterProvider.otherwise("/state1");
  //
  // Now set up the states
  $stateProvider
    .state('state1', {
      url: "/state1",
      templateUrl: "partials/state1.html"
    })
    .state('state1.list', {
      url: "/list",
      templateUrl: "partials/state1.list.html",
      controller: function($scope) {
        $scope.items = ["A", "List", "Of", "Items"];
      }
    })
    .state('state2', {
      url: "/state2",
      templateUrl: "partials/state2.html"
    })
    .state('state2.list', {
      url: "/list",
        templateUrl: "partials/state2.list.html",
        controller: function($scope) {
          $scope.things = ["A", "Set", "Of", "Things"];
        }
      })
    }]);
در ابتدا با متد ()urlRouterProvider.otherwise$ مسیر پیشفرض مشخص شده است. متد otherwise را باید از مقالات مسیریابی در AngularJS به یاد داشته باشید. سپس حالت‌های برنامه با استفاده از متد state تعریف شده است. این متد دو پارامتر ورودی دارد؛ اولی نام حالت و دومی یک شی شامل خصوصیات حالت. همانطور که می‌بینید این شی خصوصیاتی شبیه به همان‌ها که در متد ()routeProvider.when$ وجود داشت دارد. می‌شود گفت این خصوصیات همان‌ها هستند و همان عملکرد را دارند.
خصوصیت url مشخص کننده مسیر حالت است. این خصوصیت همان مقداریست که به عنوان پارامتر اول به ()routeProvider.when$ پاس می‌شد. در این پارامتر می‌شود متغیرهای url را هم به همان ترتیب تعریف کرد. مثلا اگر حالت state1 در آدرسش یک پارامتر id داشته باشد می‌شود آن را به این ترتیب تعریف کرد:
.state('state1', {
      url: "/state1/:id",
      templateUrl: "partials/state1.html"
    })
برای خواندن مقدار این متغیر باید از stateParams$ استفاده کرد:
$stateParams.id
به خصوصیت url دو حالت state1.list و state2.list دقت کنید. هردو برابر 'list/' است. یعنی هردو یک مسیر دارند؟ نه! بلکه مسیر state1.list برابر '/state1/list' و مسیر state2.list برابر '/state2/list' است. در واقع حالت state1.list یعنی list فرزند state1 و به همین ترتیب state2.list یعنی list فرزند state2. و می‌توان گفت UI-Router آدرس url حالت فرزند را، آدرسی نسبی، نسبت به url حالت پدر می‌داند. این رابطه سلسله مراتبی و پدر و فرزندی را می‌توان با استفاده از خصوصیت parent به صورت صریح‌تری مشخص کرد:
.state('list', {
    parent: "state1",
    url: "/list",
    templateUrl: "partials/state1.list.html",
    controller: function($scope) {
        $scope.items = ["A", "List", "Of", "Items"];
    }
})
.state('list', {
    parent: "state2",
    url: "/list",
    templateUrl: "partials/state2.list.html",
    controller: function($scope) {
        $scope.items = ["A", "List", "Of", "Items"];
    }
})
تا اینجای کار، اگر آدرس "state1/" وارد شود، فایل "partials/state1.html" در "ui-view" فایل "index.html" بارگذاری خواهد شد. اگر آدرس "state1/list/" وارد شود، ابتدا فایل "partials/state1.html" در "ui-view" فایل "index.html" بارگذاری شده، سپس فایل "partials/state1.list.html" در "ui-view"  آمده در فایل فایل "partials/state1.html" بارگذاری می‌شود. این همان امکان حالت‌ها و viewهای تو در تو است که UI-Router فراهم می‌کند. 
اینجا می‌توانید خروجی کدهای بالا را مشاهده کنید.
اگر مستقیما url یک حالت فرزند وارد شود، یا به عبارت دیگر، اگر بخواهیم مستقیما برنامه به حالتی که فرزند حالت دیگر است برود، UI-Router برنامه را ابتدا به حالت پدر، و پس از آن به حالت فرزند خواهد برد. حالت فرزند دو چیز را از حالت پدر به ارث می‌برد:
  1. وابستگی‌های فراهم شده در حالت پدر به وسیله "resolve"
  2. داده‌های سفارشی مشخص شده در خصوصیت data حالت پدر
استفاده از resolve در UI-Router مشابه استفاده از آن در route$  است. ولی افزودن داده‌های سفارشی کمی متفاوت است. برای افزودن داده‌های سفارشی باید از خصوصیت data یک حالت استفاده کرد:
.state('state1', {
    url: "/state1",
    templateUrl: "partials/state1.html",
    data:{
        foodata: 'addorder'
    }
})
برای دسترسی به این داده‌ها هم می‌توان از state.current.data$ استفاده کرد:
$state.current.data.foodata


Viewهای نامگذاری شده و چندگانه

یکی دیگر از قابلیت‌های کاربردی UI-Router امکان داشتن چند ui-view در هر template است (استفاده همزمان از این قابلیت و حالت‌های تو در تو، امکان مدیریت واسط کاربری را به خوبی فراهم می‌کند).  برای توضیح این قابلیت، با راهنمای UI-Router همراه شویم:
1. دستورالعمل برپایی UI-Router که در بالا آمده را اجرا کنید.
2. یک یا چند ui-view به برنامه‌تان اضافه کنید و آن‌ها را نامگذاری کنید:
<!-- index.html -->
<body>
    <div ui-view="viewA"></div>
    <div ui-view="viewB"></div>
    <!-- Also a way to navigate -->
    <a ui-sref="route1">Route 1</a>
    <a ui-sref="route2">Route 2</a>
</body>
3. حالت‌های برنامه‌تان را در روال config ماژول تعریف کنید:
myApp.config(function ($stateProvider) {
    $stateProvider
      .state('index', {
          url: "",
          views: {
              "viewA": { template: "index.viewA" },
              "viewB": { template: "index.viewB" }
          }
      })
      .state('route1', {
          url: "/route1",
          views: {
              "viewA": { template: "route1.viewA" },
              "viewB": { template: "route1.viewB" }
          }
      })
      .state('route2', {
          url: "/route2",
          views: {
              "viewA": { template: "route2.viewA" },
              "viewB": { template: "route2.viewB" }
          }
      })
});
4. خروجی کدهای بالا را اینجا مشاهده کنید.


چند نکته

UI-Router جزئیات فراوانی دارد و آنچه آمد تنها پرده برداری از آن بود. دلم می‌خواست می‌توانستم بیش از این آن را معرفی کنم، اما متاسفانه این روزها وقت آزاد کافی ندارم. در انتها می‌خواهم به چند نکته اشاره کنم:
روش controller as
برای استفاده از روش controller as در UI-Router باید به این ترتیب عمل کنید:
.state('list', {
    parent: "state1",
    url: "/list",
    templateUrl: "partials/state1.list.html",
    controller: "state1ListController as listCtrl1"
    }
})
.state('list', {
    parent: "state2",
    url: "/list",
    templateUrl: "partials/state2.list.html",
    controller: "state2ListController as listCtrl2"
    }
})

حالت‌های انتزاعی

حالت انتزاعی حالتی است که url ندارد و در نتیجه برنامه نمی‌تواند در آن حالت قرار گیرد. حالت‌های انتزاعی بسیار به درد خور هستند! مثلا فرض کنید چند حالت دارید که اشتراکاتی با هم دارند (همه باید در template مشابهی بارگذاری شود، یا وابستگی‌های یکسانی دارند، یا حتی سطح دسترسی یکسان). با تعریف یک حالت انتزاعی و جمع کردن همه وابستگی‌ها در آن، و تعریف حالت‌های مورد نظرتان به عنوان فرزندان حالت انتزاعی، می‌توانید اشتراکات حالت‌های برنامه را ساده‌تر مدیریت کنید.

حساسیت به حروف بزرگ و کوچک

در سرویس route$ با مقداردهی خصوصیت caseInsensitiveMatch می‌توانستیم مشخص کنیم که بزرگ و کوچک بودن حروف در تطبیق آدرس صفحه با پارامتر route در نظر گرفته بشود یا نه. خودمانیش اینکه url به حروف بزرگ و کوچک حساس باشد یا نه. متاسفانه در UI-Router از این امکان خبری نیست (البته فعلا) و آدرس‌های تعریف شده به حروف بزگ و کوچک حساس هستند.
 اینجا روشی برای حل این مشکل پیشنهاد شده، به این ترتیب که همه url‌های وارد شده به حروف کوچک تبدیل شود (راستش من این راه حل را نمی‌پسندم!).
چند روز قبل هم تغییراتی در کد UI-Router داده شده که امکان حساس نبودن به حروف کوچک و بزرگ فراهم شود. این تغییر هنوز در نسخه نهایی فایل UI-Router نیامده است. هرچند اگر بیاید هم آنچه تا امروز (23 اسفند 92) انجام شده مشکل را حل نمی‌کند.
اگر شما هم مثل من می‌خواهید کلا آدرس‌ها به حروف بزرگ و کوچک حساس نباشند، و فرصت حل کردن اساسی مشکل را هم ندارید به این ترتیب عمل کنید:
  • در فایل "angular-ui-router.js" عبارت "new RegExp(compiled)" را پیدا کرده و آن را به  "RegExp(compiled, 'i')" تبدیل کنید. و یا در "angular-ui-router.min.js" (هرکدام از فایل‌ها که استفاده می‌کنید) عبارت new RegExp(o) را پیدا کرده و آن را به new RegExp(o, "i")  تبدیل کنید. همین؛ صدایش را هم در نیاورید!


مطالب
دنیای چابک-قسمت اول
داخل وبلاگها و وب سایتهای فارسی زبان(مربوط به برنامه نویسی) که جستجو میکنیم شاهد  کلمه ای هستیم که تازه به چشممان میخورد:Agile(چابک). البته لازم به ذکر است این کلمه چیز جدیدی نیست و سابقه ای در حدود 10 سال دارد(February 2001 ).که جمعی از برنامه نویسان بیانیه ای را تحت عنوان چابک (Agile ) تهیه کردند که متن آن به شرح زیر است:

 ما با توسعه نرم افزار و کمک به دیگران در انجام آن . در حال کشف راه‌های بهتری برای توسعه نرم افزار هستیم. از این طریق باید دست یابیم به ارزش :
  1. افراد و تعاملات بالاتر از فرآیندها و ابزارها 
  2. نرم افزار کارکننده بالاتر از مستندات جامع
  3. مشارکت مشتری در انجام کار بالاتر از قرارداد کار
  4. پاسخگویی به تغییرات بالاتر از پیروی یک طرح
با وجود اینکه موارد سمت چپ نیز ارزشمند هستند ولی ما برای موارد سمت راست ارزش بیشتری قائل هستیم .
که این بیانیه بر پایه 12 اصل(Agile  principles)
  1. بالاترین اولویت ما جلب رضایت مشتری با تحویل زود و مداوم نرم افزاری ارزشمند می‌باشد 
  2. استقبال از تغییر نیازمندی ها، حتی در اواخر فرآیند توسعه. فرآیند‌های چابک، تغییر را در جهت مزیتِ رقابتی مشتری مهار میکنند
  3. تحویل زود به زود نرم‌افزار قابل استفاده دو، سه هفته یک بار تا دو ، سه ماه یک بار با ترجیح بر فاصله‌های زمانی کوتاه‌تر
  4. ذی نفعان کسب و کار و توسعه دهنده‌ها می‌بایست به صورت روزانه در طول پروژه با هم کار کنند
  5. پروژه‌ها را بر دوش افراد با انگیزه بنا کنید. فضای لازم رابه آنها بدهید و از نیازهای آن‌ها پشتیبانی کنید وبه آنها اعتماد کنید تا کارها را انجام دهند
  6. کارآمدترین و موثرترین روش انتقال اطلاعات به تیم توسعه و تبادل آن در میان اعضای تیم ، گفتگوی چهره به چهره است
  7. نرم افزار قابل استفاده اصلی‌ترین معیار سنجش پیشرفت است 
  8. فرآیند‌های چابک توسعه پایدار را ترویج می‌دهندحامیان مالی , توسعه دهندگان و کاربران باید بتوانند سرعت پیشرفت ثابتی را برای مدت نامحدودی حفظ کنند
  9. توجه مداوم به برتری فنی و طراحی خوب باعث افزایش چابکی می‌شود
  10. سادگی -- هنر به حداکثر رساندن مقدار کار انجام نشده -- ضروری است
  11. بهترین معماری‌ها , نیاز مندی‌ها و طراحی‌ها از تیم‌های خود سازمانده پدید آور می‌شود
  12. در فواصل منظم , تیم برچگونگی موثرتر شدن تامل وتفکر می‌نماید و سپس تیم رفتار خود را بر اساس بازتاب این تفکر تنظیم و هم سو می‌نماید
متاسفانه در ایران حالا یا به علت سواد کم و یا به هر علتی از این بیانیه برداشتهایی متفاوت و غلط عده ای اونو به بازی تشبیه کردن و عده ای هم با اون کار میکنن ولی هیچکدوم از اصل‌های اونو رعایت نمیکنن و بعد که پروژه شکست خورد میگن:متدولوژی خوب نبود و ... .
وقتی کتاب Agile Principles, Patterns, and Practices in C# رو مطالعه میکنید به این نتیجه میرسید که بیشترین چیزی که تاکید داره روی ارتباطات هستش.
قصد دارم در قالب چند پست به شما این اصول رو معرفی کنم.
نظرات مطالب
استفاده از فیلدهای XML در NHibernate
سلام.
من nHibernate بلد نیستم اما قسمتی که درباره فرم ساز ها صحبت کردید جالب بود.
این فرم سازی که شما ازش صحبت کردید User Mode بود حالا اگر فرم سازی بخواهیم توسعه بدیم که Developer Mode باشه نظرتون چیه؟
مثلا از روی یک Table فرم خام رو صورت Html در بیاورد.
اشتراک‌ها
مهاجرت از firebug به Firefox Developer Tools

When migrating from Firebug to the Firefox Developer Tools, you may wonder where the features you loved in Firebug are available in the Developer Tools. The following list aims to help Firebug users to find their way into the Developer Tools. 

مهاجرت از firebug  به Firefox Developer Tools
مطالب
Vue.js - نحوه‌ی استفاده از list item ها و تخصیص item - قسمت چهارم
در این قسمت به بحث item list‌‌ها پرداخته می‌شود. روند کلی بدین صورت است که ابتدا یک آرایه را ایجاد کرده (با مقادیر مشخص) و سپس درون تگ مورد نظری از صفحه، آرایه را فراخوانی می‌کنیم.
برای این منظور ابتدا یک vue جدید را ساخته و سپس آرایه مورد نظر را تعریف کرده و آیتم‌های آرایه را مشخص می‌کنیم. به صورت زیر:
     var dotnettips = new Vue({
         el: '#dotnettips',
         data: {
            items: [
                  { message: 'one' },
                  { message: 'two' }
    ]
  }
});
در کد بالا یک ویژگی با نام dotnettips تعریف شده و مقادیری به آن اختصاص داده شده است.
حال زمان آن فرارسیده است تا بدنه اصلی ساختار صفحه را بنویسیم.
<html>
    <body>
    <ul id="dotnettips">
       <li v-for="item in items">
           {{ item.message }}
       </li>
    </ul>
  
    <script src="https://unpkg.com/vue@2.2.6">
    </script>

    <script type="text/javascript">
         var dotnettips = new Vue({
         el: '#dotnettips',
         data: {
            items: [
                  { message: 'one' },
                  { message: 'two' }
            ]
          }
        });
        </script>      
        </body>
</html>
برای نمایش خروجی کار به صورت دلخواه، ابتدا یک تگ ul و درون آن تگ li را ایجاد کردیم و سپس با استفاده از دستور v-for، اقدام به فراخوانی list item مورد نظر کردیم و سپس ویژگی مورد نظر را که همان item.message می‌باشد، درون علامت {{ }} قرار دادیم، تا خروجی نهایی به نمایش درآید.
 .حال با اجرای کد فوق تمامی آیتم‌های مورد نظر روی صفحه به نمایش در می‌آیند 
.دقت نمائید که کدهای فوق و بدنه اسکریپت به دلخواه نوشته شده‌است و شما طبق نیاز خود می‌توانید آن‌ها را تغییر دهید
مطالب
صفحه بندی، مرتب سازی و جستجوی پویای اطلاعات به کمک Kendo UI Grid
پس از آشنایی مقدماتی با Kendo UI DataSource، اکنون می‌خواهیم از آن جهت صفحه بندی، مرتب سازی و جستجوی پویای سمت سرور استفاده کنیم. در مثال قبلی، هر چند صفحه بندی فعال بود، اما پس از دریافت تمام اطلاعات، این اعمال در سمت کاربر انجام و مدیریت می‌شد.



مدل برنامه

در اینجا قصد داریم لیستی را با ساختار کلاس Product در اختیار Kendo UI گرید قرار دهیم:
namespace KendoUI03.Models
{
    public class Product
    {
        public int Id { set; get; }
        public string Name { set; get; }
        public decimal Price { set; get; }
        public bool IsAvailable { set; get; }
    }
}


پیشنیاز تامین داده مخصوص Kendo UI Grid

برای ارائه اطلاعات مخصوص Kendo UI Grid، ابتدا باید درنظر داشت که این گرید، درخواست‌های صفحه بندی خود را با فرمت ذیل ارسال می‌کند. همانطور که مشاهده می‌کنید، صرفا یک کوئری استرینگ با فرمت JSON را دریافت خواهیم کرد:
 /api/products?{"take":10,"skip":0,"page":1,"pageSize":10,"sort":[{"field":"Id","dir":"desc"}]}
سپس این گرید نیاز به سه فیلد، در خروجی JSON نهایی خواهد داشت:
{
"Data":
[
{"Id":1500,"Name":"نام 1500","Price":2499.0,"IsAvailable":false},
{"Id":1499,"Name":"نام 1499","Price":2498.0,"IsAvailable":true}
],
"Total":1500,
"Aggregates":null
}
فیلد Data که رکوردهای گرید را تامین می‌کنند. فیلد Total که بیانگر تعداد کل رکوردها است و Aggregates که برای گروه بندی بکار می‌رود.

می‌توان برای تمام این‌ها، کلاس و Parser تهیه کرد و یا ... پروژه‌ی سورس بازی به نام  Kendo.DynamicLinq نیز چنین کاری را میسر می‌سازد که در ادامه از آن استفاده خواهیم کرد. برای نصب آن تنها کافی است دستور ذیل را صادر کنید:
 PM> Install-Package Kendo.DynamicLinq
Kendo.DynamicLinq به صورت خودکار System.Linq.Dynamic را نیز نصب می‌کند که از آن جهت صفحه بندی پویا استفاده خواهد شد.


تامین کننده‌ی داده سمت سرور

همانند مطلب کار با Kendo UI DataSource ، یک ASP.NET Web API Controller جدید را به پروژه اضافه کنید و همچنین مسیریابی‌های مخصوص آن‌را به فایل global.asax.cs نیز اضافه نمائید.
using System.Linq;
using System.Net.Http;
using System.Web.Http;
using Kendo.DynamicLinq;
using KendoUI03.Models;
using Newtonsoft.Json;

namespace KendoUI03.Controllers
{
    public class ProductsController : ApiController
    {
        public DataSourceResult Get(HttpRequestMessage requestMessage)
        {
            var request = JsonConvert.DeserializeObject<DataSourceRequest>(
                requestMessage.RequestUri.ParseQueryString().GetKey(0)
            );

            var list = ProductDataSource.LatestProducts;
            return list.AsQueryable()
                       .ToDataSourceResult(request.Take, request.Skip, request.Sort, request.Filter);
        }
    }
}
تمام کدهای این کنترلر همین چند سطر فوق هستند. با توجه به ساختار کوئری استرینگی که در ابتدای بحث عنوان شد، نیاز است آن‌را توسط کتابخانه‌ی JSON.NET تبدیل به یک نمونه از DataSourceRequest نمائیم. این کلاس در Kendo.DynamicLinq تعریف شده‌است و حاوی اطلاعاتی مانند take و skip کوئری LINQ نهایی است.
ProductDataSource.LatestProducts صرفا یک لیست جنریک تهیه شده از کلاس Product است. در نهایت با استفاده از متد الحاقی جدید ToDataSourceResult، به صورت خودکار مباحث صفحه بندی سمت سرور به همراه مرتب سازی اطلاعات، صورت گرفته و اطلاعات نهایی با فرمت DataSourceResult بازگشت داده می‌شود. DataSourceResult نیز در Kendo.DynamicLinq تعریف شده و سه فیلد یاد شده‌ی Data، Total و Aggregates را تولید می‌کند.

تا اینجا کارهای سمت سرور این مثال به پایان می‌رسد.


تهیه View نمایش اطلاعات ارسالی از سمت سرور

اعمال مباحث بومی سازی
<head>
    <meta charset="utf-8" />
    <meta http-equiv="Content-Language" content="fa" />
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />

    <title>Kendo UI: Implemeting the Grid</title>

    <link href="styles/kendo.common.min.css" rel="stylesheet" type="text/css" />
    <!--شیوه نامه‌ی مخصوص راست به چپ سازی-->
    <link href="styles/kendo.rtl.min.css" rel="stylesheet" />
    <link href="styles/kendo.default.min.css" rel="stylesheet" type="text/css" />
    <script src="js/jquery.min.js" type="text/javascript"></script>
    <script src="js/kendo.all.min.js" type="text/javascript"></script>

    <!--محل سفارشی سازی پیام‌ها و مسایل بومی-->
    <script src="js/cultures/kendo.culture.fa-IR.js" type="text/javascript"></script>
    <script src="js/cultures/kendo.culture.fa.js" type="text/javascript"></script>
    <script src="js/messages/kendo.messages.en-US.js" type="text/javascript"></script>

    <style type="text/css">
        body {
            font-family: tahoma;
            font-size: 9pt;
        }
    </style>

    <script type="text/javascript">
        // جهت استفاده از فایل: kendo.culture.fa-IR.js
        kendo.culture("fa-IR");
    </script>
</head>
- در اینجا چند فایل js و css جدید اضافه شده‌اند. فایل kendo.rtl.min.css جهت تامین مباحث RTL توکار Kendo UI کاربرد دارد.
- سپس سه فایل kendo.culture.fa-IR.js، kendo.culture.fa.js و kendo.messages.en-US.js نیز اضافه شده‌اند. فایل‌های fa و fa-Ir آن هر چند به ظاهر برای ایران طراحی شده‌اند، اما نام ماه‌های موجود در آن عربی است که نیاز به ویرایش دارد. به همین جهت به سورس این فایل‌ها، جهت ویرایش نهایی نیاز خواهد بود که در پوشه‌ی src\js\cultures مجموعه‌ی اصلی Kendo UI موجود هستند (ر.ک. فایل پیوست).
- فایل kendo.messages.en-US.js حاوی تمام پیام‌های مرتبط با Kendo UI است. برای مثال«رکوردهای 10 تا 15 از 1000 ردیف» را در اینجا می‌توانید به فارسی ترجمه کنید.
- متد kendo.culture کار مشخص سازی فرهنگ بومی برنامه را به عهده دارد. برای مثال در اینجا به fa-IR تنظیم شده‌است. این مورد سبب خواهد شد تا از فایل kendo.culture.fa-IR.js استفاده گردد. اگر مقدار آن‌را به fa تنظیم کنید، از فایل kendo.culture.fa.js کمک گرفته خواهد شد.

راست به چپ سازی گرید
تنها کاری که برای راست به چپ سازی Kendo UI Grid باید صورت گیرد، محصور سازی div آن در یک div با کلاس مساوی k-rtl است:
    <div class="k-rtl">
        <div id="report-grid"></div>
    </div>
k-rtl و تنظیمات آن در فایل kendo.rtl.min.css قرار دارند که در ابتدای head صفحه تعریف شده‌است.

تامین داده و نمایش گرید

در ادامه کدهای کامل DataSource و Kendo UI Grid را ملاحظه می‌کنید:
    <script type="text/javascript">
        $(function () {
            var productsDataSource = new kendo.data.DataSource({
                transport: {
                    read: {
                        url: "api/products",
                        dataType: "json",
                        contentType: 'application/json; charset=utf-8',
                        type: 'GET'
                    },
                    parameterMap: function (options) {
                        return kendo.stringify(options);
                    }
                },
                schema: {
                    data: "Data",
                    total: "Total",
                    model: {
                        fields: {
                            "Id": { type: "number" }, //تعیین نوع فیلد برای جستجوی پویا مهم است
                            "Name": { type: "string" },
                            "IsAvailable": { type: "boolean" },
                            "Price": { type: "number" }
                        }
                    }
                },
                error: function (e) {
                    alert(e.errorThrown);
                },
                pageSize: 10,
                sort: { field: "Id", dir: "desc" },
                serverPaging: true,
                serverFiltering: true,
                serverSorting: true
            });

            $("#report-grid").kendoGrid({
                dataSource: productsDataSource,
                autoBind: true,
                scrollable: false,
                pageable: true,
                sortable: true,
                filterable: true,
                reorderable: true,
                columnMenu: true,
                columns: [
                    { field: "Id", title: "شماره", width: "130px" },
                    { field: "Name", title: "نام محصول" },
                    {
                        field: "IsAvailable", title: "موجود است",
                        template: '<input type="checkbox" #= IsAvailable ? checked="checked" : "" # disabled="disabled" ></input>'
                    },
                    { field: "Price", title: "قیمت", format: "{0:c}" }
                ]
            });
        });
    </script>
- با تعاریف مقدماتی Kendo UI DataSource پیشتر آشنا شده‌ایم و قسمت read آن جهت دریافت اطلاعات از سمت سرور کاربرد دارد.
- در اینجا ذکر contentType الزامی است. زیرا ASP.NET Web API بر این اساس است که تصمیم می‌گیرد، خروجی را به صورت JSON ارائه دهد یا XML.
- با استفاده از parameterMap، سبب خواهیم شد تا پارامترهای ارسالی به سرور، با فرمت صحیحی تبدیل به JSON شده و بدون مشکل به سرور ارسال گردند.
- در قسمت schema باید نام فیلدهای موجود در DataSourceResult دقیقا مشخص شوند تا گرید بداند که data را باید از چه فیلدی استخراج کند و تعداد کل ردیف‌ها در کدام فیلد قرار گرفته‌است.
- نحوه‌ی تعریف model را نیز در اینجا ملاحظه می‌کنید. ذکر نوع فیلدها در اینجا بسیار مهم است و اگر قید نشوند، در حین جستجوی پویا به مشکل برخواهیم خورد. زیرا پیش فرض نوع تمام فیلدها string است و در این حالت نمی‌توان عدد 1 رشته‌ای را با یک فیلد از نوع int در سمت سرور مقایسه کرد.
- در اینجا serverPaging، serverFiltering و serverSorting نیز به true تنظیم شده‌اند. اگر این مقدار دهی‌ها صورت نگیرد، این اعمال در سمت کلاینت انجام خواهند شد.

پس از تعریف DataSource، تنها کافی است آن‌را به خاصیت dataSource یک kendoGrid نسبت دهیم.
- autoBind: true سبب می‌شود تا اطلاعات DataSource بدون نیاز به فراخوانی متد read آن به صورت خودکار دریافت شوند.
- با تنظیم scrollable: false، اعلام می‌کنیم که قرار است تمام رکوردها در معرض دید قرارگیرند و اسکرول پیدا نکنند.
- pageable: true صفحه بندی را فعال می‌کند. این مورد نیاز به تنظیم pageSize: 10 در قسمت DataSource نیز دارد.
- با sortable: true مرتب سازی ستون‌ها با کلیک بر روی سرستون‌ها فعال می‌گردد.
- filterable: true به معنای فعال شدن جستجوی خودکار بر روی فیلدها است. کتابخانه‌ی Kendo.DynamicLinq حاصل آن‌را در سمت سرور مدیریت می‌کند.
- reorderable: true سبب می‌شود تا کاربر بتواند محل قرارگیری ستون‌ها را تغییر دهد.
- ذکر columnMenu: true اختیاری است. اگر ذکر شود، امکان مخفی سازی انتخابی ستون‌ها نیز مسیر خواهد شد.
- در آخر ستون‌های گرید مشخص شده‌اند. با تعیین "{format: "{0:c سبب نمایش فیلدهای قیمت با سه رقم جدا کننده خواهیم شد. مقدار ریال آن از فایل فرهنگ جاری تنظیم شده دریافت می‌گردد. با استفاده از template تعریف شده نیز سبب نمایش فیلد bool به صورت یک checkbox خواهیم شد.


کدهای کامل این مثال را از اینجا می‌توانید دریافت کنید:
KendoUI03.zip