مسیرراه‌ها
React 16x
پیش نیاز ها
کامپوننت ها
ترکیب کامپوننت ها
طراحی یک گرید
مسیریابی 
کار با فرم ها
ارتباط با سرور
احراز هویت و اعتبارسنجی کاربران 
React Hooks  
توزیع برنامه

مدیریت پیشرفته‌ی حالت در React با Redux و Mobx   

       Redux
       MobX  

مطالب تکمیلی 
    مطالب
    قابل ویرایش کننده‌ی فوق العاده x-editable ؛ قسمت اول
    من در یکی از پروژه‌ها از Kendo UI Treeview استفاده کردم و قصد داشتم قابلیت تغییر نام را به گره‌ها بدهم. به همین جهت پس از جستجو به x-editable برخوردم. این کتابخانه‌ی جاواسکریپتی در ابتدا برای قالب‌های بوت استراپ طراحی شده بود که در حال حاضر اینگونه نیست و به راحتی در هر پروژه‌ای که فقط جی کوئری صدا زده شده باشد، قابل اجرا است و نسخه‌ی مخصوص Angular آن هم در این آدرس قرار دارد. همچنین این قابلیت اختیاری و پیش فرض را دارد که به طور خودکار اطلاعات تغییر یافته را به سمت url ایی که شما تعیین می‌کنید، ارسال کند. برای همین نیازی به استفاده جداگانه از jQuery Ajax برای ارسال اطلاعات نیست و البته در انتهای مقاله هم هنگام استفاده از درخت کندو به مشکلی برخوردم که آن را هم بررسی می‌کنیم.

    وارد کردن کتابخانه ها
    این کتابخانه شامل دو فایل css و JS می‌باشد که بسته به محیطی که در آن کار می‌کنید متفاوت هستند. در این صفحه شما می‌توانید برای 4 محیط Jquery ,JqueryUI , Bootstrap2 و Bootsrap3 بسته‌ی مخصوصش را یا به صورت دانلود فایل‌ها یا از طریق CDN دریافت نمایید. در اینجا هم یک دمو از قابلیت‌های آن قابل مشاهده است.

    برای شروع، کتابخانه‌ی مورد نظر خود را دریافت و آن‌ها را به صفحه‌ی خود اضافه نمایید. در صورتیکه از Bootstrap استفاده می‌کنید، ابتدا فایل‌های زیر را اضافه کنید:
        <link href="//netdna.bootstrapcdn.com/bootstrap/3.0.0/css/bootstrap.min.css" rel="stylesheet">
        <script src="http://code.jquery.com/jquery-2.0.3.min.js"></script> 
        <script src="//netdna.bootstrapcdn.com/bootstrap/3.0.0/js/bootstrap.min.js"></script>
    سپس فایل‌های x-editable را صدا بزنید.

    اولین حالتیکه میتوانید با این کتابخانه کار کنید، استفاده از خاصیت *-data است. نمونه زیر را در نظر بگیرید:
      <a href="#" id="favsite" data-type="text" data-pk="1" data-url="@Url.Action(MVC.Categories.EditCategory())" 
    data-title="Enter your favorite site">dotnettips.info</a>
    به تعداد هر عنصری که نیاز است، اینکار را انجام دهید و به هر کدام یک id انتساب دهید. بعد از آن کد زیر را در قسمت script بنویسید:
     $(document).ready(function () {
            $('#favsite').editable();
        });
    در صورتیکه قصد دارید خصوصیاتی از اشیاء را که برای همه‌ی عنصرهای معرفی شده یکسان است، معرفی کنید می‌توانید از کد زیر بهره ببرید:
     $(document).ready(function () {
            $.fn.editable.defaults.mode = 'inline';
            $('#favsite').editable();
        });
    کد بالا حالت ویرایش تمام عناصر معرفی شده را به inline تغییر می‌دهد.

    حالت بعدی که می‌توان استفاده کرد به شکل زیر است:
      <a href="#" id="favsite" >dotnettips.info</a>

      $.fn.editable.defaults.mode = 'inline';
        $(document).ready(function () {
            $('#favsite').editable({
                type: 'text',
                pk: 1,
                url: '@Url.Action(MVC.Categories.EditCategory())',
                title: 'Enter your favorite site'
            });
        });

    خوبی این روش این است که می‌توان اطلاعات بیشتری چون رویدادها را به آن پاس داد. تا الان با نحوه‌ی انتساب آن به اشیاء آشنا شدیم. اجازه دهید تا با خصوصیات آن آشنا شویم.


     AjaxOptions
     همانطور که متوجه شدید به طور خودکار اطلاعات ویرایش شده، به سمت آدرس داده شده، به شیوه Post ارسال می‌گردند. در صورتیکه قصد دست بردن در نوع درخواست را دارید، می‌توانید از این ویژگی استفاده کنید:
        ajaxOptions: {
            type: 'put',
            dataType: 'json'
        }
     Anim
     این ویژگی که تنها در حالت inline پاسخ می‌دهد، می‌تواند زمان بسته شدن x-editable را تغییر دهد که به طور پیش فرض با false مقداردهی شده است. جهت تغییر زمان بسته شدن، کد زیر را وارد نمایید:
    anim:'false'
    //or
    anim: {
                    duration: 2000
                }
     autotext  در انتهای جدول آمده است.
     defaultValue 
     در صورتیکه عنصر مورد نظر محتوایی نداشته باشد و این خصوصیت را مقداردهی کنید، موقع ویرایش، این عبارت تعیین شده نمایش می‌یابد. در مثال بالا باید متن تگ a را حذف کرده تا نتیجه را ببینید: (البته فیلد value نباید مقداری داشته باشد)
    defaultValue: 'default val'
    //or
    defaultValue: undefined
    //or
    defaultValue: null
    در بقیه‌ی حالات، ویرایشگر خالی از متن خواهد بود و مقدار پیش فرض آن نال است.
     disabled  false کردن این ویژگی باعث غیرفعال شدن x-editable بر روی کنترل جاری میگردد.
     display 
    خاصیت  display یا مقدار بولین false را دریافت می‌کند، یا نال، یا یک تابع callback را می‌توان به آن پاس داد. این خصوصت زمانی صدا زده می‌شود که اطلاعات به سمت آدرس سرور رفته و با موفقیت بازگشت داده می‌شوند (در صورتی که این ویژگی غیرفعال باشد، بلافاصه بعد از تایید کاربر، از اطلاعات وارد شده صدا زده می‌شود) و سپس متن جدید عنصر تغییر می‌یابد. حال اگر این خاصیت نال که مقدار پیش فرض آن است باشد، متن تغییر می‌یابد. ولی اگر false باشد، متن سابق باقی خواهد ماند و در صورتیکه تابعی به آن پاس داده باشید، طبق تابع شما عمل خواهد کرد.
    پارامترهایی که تابع شما می‌تواند داشته باشد به شرح زیر است:
    value : مقدار جدید
    response : پاسخ سرور ( در صورتی که ارسال از طریق Ajax صورت گرفته باشد)

    و در صورتیکه عنصر شما checlklist یا select باشد که حاوی منبعی از مقادیر هست، مقادیرشان در قالب یک آرایه با نام sourceData بازگشت خواهد خورد. برای دسترسی به آیتم‌های انتخابی هم از کد زیر استفاده می‌کنیم:
    $.fn.editableutils.itemsByValue(value, sourceData)
     emptyclass  معرفی یک کلاس css برای موقعیکه عنصر خالی است.
     emptytext  در صورتی خالی بودن عنصر، این متن را برای عنصر نمایش بده.
     highlight   بعد از به روز رسانی متن عنصر، آن را با این رنگ highlight خواهد کرد و کد رنگی باید در مبنای هگز باشد. مقدار پیش فرض آن false است.
     mode
     دو حالت نمایشی دارد که پیش فرض آن popup است و با باز کردن یک پنجره، مقدار جدید را دریافت می‌کند. مورد بعدی inline است که به جای باز کردن پنجره، متن عنصر را به حالت ویرایش تغییر میدهد.
     name  نام فیلدی که مقدارش تغییر می‌کند.
     onblur زمانی که کاربر فوکوس را از ویرایشگر  می‌گیرد، ویرایشگر چه پاسخی باید به آن بدهد، باز بماند؟ ignore ، بسته شود؟ cancel و یا مقدار داده شده را تایید کند؟submit
     params
    پارامترهای درخواست ایجکسی که کنترل در حالت پیش فرض ارسال می‌کند؛ شامل Pk که آن را با id رکورد پر می‌کنیم. name نام فیلدی که تغییر یافته است و value که مقدار جدید است. در صورتیکه دوست دارید اطلاعات اضافی‌تری نیز ارسال شوند، می‌توانید از این خاصیت استفاده کنید و پارامترها را در قالب Object به آن پاس کنید. ولی اگر بخواهید در کل همه‌ی پارامترها را رونویسی کنید باید یک تابع را به آن پاس کنید:

      params: function(params) {
            //در این حالت پارامترهای پیش فرض ارسال نشده 
    و تنها پارامترهای معرفی شده در این تابع ارسال می‌شوند
            params.a = 1;
            return params;
        }

     pk  کلید اصلی رکورد شما در دیتابیس یا هان id است. در صورتی که از کلیدهای ترکیبی استفاده می‌کنید، نگران نباشید فکر آن را هم کرده اند.
    //کلید عدد
    pk:1,
    //کلید رشته ای
    pk:'dp123'
    //کلید ترکیبی
    pk:{id: 1, lang: 'en'}
    
    //معرفی یک تابع به آن و بازگشت 
    یکی از مقادیر بالا بعد از محاسبات pk:function() { }
     Placement  این ویژگی فقط به درد حالت Popup می‌خورد که پنجره را کجای عنصر نمایش دهد و شامل چهار مقدار left,right,top,bottom می‌شود.
     saveonchange  زمانی که مقدار جدید، برابر مقدار فعلی باشد و این خاصیت false باشد، هیچ تغییر‌ی رخ نخواهد داد. ولی اگر برابر true باشد ،مقدار جدید اسال و جایگزین مقدار فعلی خواهد شد. مقدار پیش فرض آن false است.
     selector
      با استفاده از این خصوصیت در عنصر انتخابی به دنبال عناصری که در selector تعیین شده می‌گردد و حالت ویرایش را روی آن‌ها فعال می‌کند.
    در این حالت استفاده از خصوصیات emptytext و autotext در ابتدای امر ممکن نیست و بعد از اولین کلیک قابل استفاده هستند.
    نکته بعدی اینکه شما باید کلاس‌های زیر را دستی اضافه کنید.
    کلاس  editable-click برای همه کنترل‌ها وکلاس editable-empty به کنترل‌های بدون مقدار و برای مقداردهی کنترلهای بدون مقدار میتوان از خاصیت ''=data-value استفاده کرد.
        <div id="user">
          <!-- empty -->
          <a href="#" data-name="username" 
    data-type="text" data-value="" 
    title="Username">Empty</a>
          <!-- non-empty -->
          <a href="#" data-name="group" 
    data-type="select" data-source="/groups"
     data-value="1" title="Group">Operator</a>
        </div>     
         
        <script>
        $('#user').editable({
            selector: 'a',
            url: '/post',
            pk: 1
        });
        </script>
     send  سه مقدار auto,always و never را دریافت می‌کند. موقعی که شما آن را روی auto تنظیم کنید؛ در صورتی مقادیر به سمت سرور ارسال می‌شوند که دو خاصیت url و pk تعریف شده باشند. در غیر این صورت ویرایش فقط در حالت محلی و روی سیستم کاربر رخ خواهد داد.
     showbuttons   در صورتیکه با false مقداردهی شود، تایید فرم به طور خودکار انجام می‌گیرد و اگر با یکی از مقادیر left یا Bottom پر شود، دکمه‌ها را در آن قسمت نشان می‌دهد.
     success
     اطلاعات به سمت سرور رفته و با موفقیت با کد 200 بازگشت داده شده‌اند. در مستندات نوشته است، هر کد وضعیتی غیر از 200 بازگشت داده شود، به سمت خاصیت error هدایت می‌شو.د ولی آن طور که من با httpresponsemessage تست کردم، چنین چیزی را مشاهده نکردم و مجددا success صدا زده شد. پس بهتر هست داده‌ای را که به سمت کنترل برگشت می‌دهید، خودتان کنترل کنید. به خصوص اگر انتقال اطلاعات صحیح باشد. ولی اگر در دیتابیس، تغییر با خطا روبرو گردد بهتر است نتیجه‌ی آن ارسال شده و از تغییر مقدار فعلی ممانعت به عمل آورید.
        success: function(response, newValue) {
            if(!response.success) return response.msg;
        }
     toggle  اگر قصد دارید که باز و بسته کردن ویرایشگر را بر عهده‌ی مثلا یک دکمه‌ی روی صفحه بگذارید، این خصوصیت به شما کمک می‌کند:
        $('#edit-button').click(function(e) {
            e.stopPropagation();
            $('#favsite').editable('toggle');
        });
    به جای toggle نیز می‌توان از show و hide هم استفاده کرد. وجود عبارت e.stopPropagation جهت باز شدن صحیح ویرایشگر الزامی است.

     type نوع ویرایشی را که قرار است انجام گیرد، مشخص می‌کند. text برای متن، date برای تاریخ، textarea جهت متون طولانی‌تر نسبت به text و بسیاری از موارد دیگر
     unsavedclass  این کلاس موقعی اعمال می‌گردد که اطلاعاتی را ویرایش کرده‌اید، ولی اطلاعاتی به سمت سرور ارسال نشده است. مثلا pk مقداردهی نشده یا send=never قرار داید و یا اینکه به صورت محلی ذخیره می‌کنید و می‌خواهید در آخر همه‌ی اطلاعات را ارسال کنید.
    این خاصیت به طور پیش فرض با کلاس editable-unsaved مقداردهی شده که می‌توانید با نال کردن، از شرش خلاص شوید.
     url
    این خاصیت با آدرس سمت سرور پر می‌شود. ولی میتوان به آن یک تابع هم پاس کرد که این تابع جایگزین درخواست ایجکسی خودش خواهد شد و برای بازگشت دادن نتیجه‌ی این درخواست به سمت تابع‌های callback خودش می‌توانید یک deferred object را برگشت دهید:

     url: function(params) {
            var d = new $.Deferred;
            if(params.value === 'abc') {
    //returning error via deferred object 
    
     return d.reject('error message'); 
            } else {
                //async saving data in js model
                someModel.asyncSaveMethod({
                   ..., 
                   success: function(){
                      d.resolve();
                   }
                }); 
                return d.promise();
            }
        }

     validate
     مقدار پیش فرض آن نال است و می‌توان به آن یک تابع را جهت اعتبارسنجی سمت کلاینت پاس داد. به عنوان آرگومان، مقدار جدیدی را ارسال کرده و در آن به اعتبارسنجی می‌پردازید. در صورتی که مقدار، نامعتبر باشد، می‌توانید یک پیام خطا از نوع رشته‌ای را برگردانید.

    در صورتی که از نسخه‌ی 1.5.1 به بعد استفاده می‌کنید، دریافت یک object با مقادیر زیر نیز ممکن شده است:
    newValue: مقدار جدید و جایگزین مقدار غیر معتبر.
    msg : پیام خطا.
    به کدهای زیر در سه حالت نگاه کنید:
    validate: function (value) {
                    if ($.trim(value) == '') {
    //در تمامی نسخه‌های یک پیام متنی باز میگردد
                        return 'This field is required';
    //1.5.1
    //یک مقدار جدید برگشت میدهد که بلافاصله آن را
    // تایید میکند و متن عنصر به روز می‌شود
                        return { newValue: 'validated' };
    //متن جدید ار ارسال کرده و پیام هشدار را نشان میدهد 
    //ولی تایید نمی‌کند و منتظر تایید کاربر است
                        return { newValue: 'validated',
     msg: 'This field is required' }; } }

     value مقدار پیش فرضی که در ویرایشگر نشان می‌دهد و اگر مقداردهی نشود، از متن عنصر استفاده می‌کند.
     autotext  سه مقدار دارد auto (پیش فرض)،always و never.
    موقعیکه عنصر شما متنی نداشته باشد و روی auto تنظیم شده باشد، مقدار value را به عنوان متن عنصر نمایش می‌دهد.
    always کاری ندارد که عنصر شما متن دارد یا خالی است؛ مقدار value به آن انتساب داده خواهد شد.
    never هیچگاه.

      در قسمت بعدی که قسمت پایانی است مطالب را ادامه می‌دهیم.
    مطالب
    React 16x - قسمت 18 - کار با فرم‌ها - بخش 1 - دریافت ورودی‌ها از کاربر
    تقریبا تمام برنامه‌ها نیاز دارند فرم‌های مخصوصی را داشته باشند. به همین جهت در این قسمت، برنامه‌ی نمایش لیست فیلم‌ها را که تا این مرحله تکمیل کردیم، با افزودن تعدادی فرم بهبود می‌بخشیم؛ مانند فرم لاگین، فرم ثبت نام، فرمی برای ثبت و ویرایش فیلم‌ها و یک فرم جستجوی سریع در لیست فیلم‌های موجود.


    ایجاد فرم لاگین

    فرم لاگینی را که به برنامه‌ی نمایش لیست فیلم‌های تکمیل شده‌ی تا قسمت 17، اضافه خواهیم کرد، یک فرم بوت استرپی است و می‌توانید جزئیات بیشتر مزین سازی المان‌های این نوع فرم‌ها را با کلاس‌های بوت استرپ، در مطلب «کار با شیوه‌نامه‌های فرم‌ها در بوت استرپ 4» مطالعه کنید.
    در ابتدا فایل جدید src\components\loginForm.jsx را ایجاد کرده و سپس توسط میان‌برهای imrc و cc در VSCode، ساختار ابتدایی کامپوننت جدید LoginForm را ایجاد می‌کنیم:
    import React, { Component } from "react";
    
    
    class LoginForm extends Component {
      render() {
        return <h1>Login</h1>;
      }
    }
    
    export default LoginForm;
    در ادامه یک Route جدید را در فایل app.js برای این فرم، با مسیر login/ و کامپوننت LoginForm، در ابتدای Switch موجود، تعریف می‌کنیم:
    import LoginForm from "./components/loginForm";
    //...
    
    function App() {
      return (
        <React.Fragment>
          <NavBar />
          <main className="container">
            <Switch>
              <Route path="/login" component={LoginForm} />
              <Route path="/movies/:id" component={MovieForm} />
              // ...
            </Switch>
          </main>
        </React.Fragment>
      );
    }
    پس از تعریف این مسیریابی، نیاز است لینک آن‌را نیز به منوی راهبری سایت اضافه کنیم. به همین جهت در فایل navBar.jsx که آن‌را در قسمت قبل تکمیل کردیم، در انتهای لیست موجود و پس از Rentals، لینک لاگین را نیز قرار می‌دهیم:
    <NavLink className="nav-item nav-link" to="/login">
       Login
    </NavLink>
    که در نهایت حاصل این تغییرات، به صورت زیر در مرورگر ظاهر می‌شود:


    اکنون نوبت به افزودن فرم بوت استرپی لاگین به فایل loginForm.jsx رسیده‌است:
    import React, { Component } from "react";
    
    
    class LoginForm extends Component {
      render() {
        return (
          <form>
            <div className="form-group">
              <label htmlFor="username">Username</label>
              <input id="username" type="text" className="form-control" />
            </div>
            <div className="form-group">
              <label htmlFor="password">Password</label>
              <input id="password" type="password" className="form-control" />
            </div>
            <button className="btn btn-primary">Login</button>
          </form>
        );
      }
    }
    
    export default LoginForm;
    توضیحات:
    - ابتدا المان form به صفحه اضافه می‌شود.
    - سپس هر ورودی، داخل یک div با کلاس form-group، محصور می‌شود. کار آن تبدیل یک برچسب و فیلد ورودی، به یک گروه از ورودی‌های بوت استرپ است.
    - در اینجا هر برچسب دارای یک ویژگی for است. اما چون قرار است عبارات jsx، به معادل‌های جاوا اسکریپتی ترجمه شوند، نمی‌توان از واژه‌ی کلیدی for در اینجا استفاده کرد. به همین جهت از معادل react ای آن که htmlFor است، در کدهای فوق استفاده کرده‌ایم؛ شبیه به نکته‌ای که در مورد تبدیل ویژگی class به className وجود دارد. مقدار هر ویژگی htmlFor نیز به id فیلد ورودی متناظر با آن تنظیم می‌شود. به این ترتیب اگر کاربر بر روی این برچسب کلیک کرده و آن‌را انتخاب کند، فیلد متناظر با آن، دارای focus می‌شود.
    - فیلدهای ورودی نیز دارای کلاس form-control هستند.

    با این خروجی نهایی در مرورگر:



    مدیریت ارسال فرم‌ها

    به صورت پیش فرض و استاندارد، دکمه‌ی افزوده شده‌ی به المان form، سبب ارسال اطلاعات آن به سرور و سپس بارگذاری کامل صفحه می‌شود. این رفتاری نیست که در یک برنامه‌ی SPA مدنظر باشد. برای مدیریت این حالت، می‌توان از رخ‌داد onSubmit هر المان فرم، استفاده کرد:
    class LoginForm extends Component {
      handleSubmit = e => {
        console.log("handleSubmit", e);
        e.preventDefault();
    
        // call the server
      };
    
      render() {
        return (
          <form onSubmit={this.handleSubmit}>
          //...
    در اینجا یک متد رویدادگردان را برای رخ‌داد onSubmit تعریف کرده‌ایم که توسط آن رخ‌داد جاری، دریافت و متد preventDefault آن فراخوانی می‌شود تا دیگر پس از کلیک بر روی دکمه‌ی submit، حالت پیش‌فرض و استاندارد full page reload و post back به سمت سرور، رخ ندهد.


    دسترسی مستقیم به المان‌های فرم‌ها

    پس از فراخوانی متد preventDefault، کار مدیریت ارسال فرم به سرور را باید خودمان مدیریت کنیم و دیگر رخ‌داد full post back استاندارد به سمت سرور را نخواهیم داشت. در جاوا اسکریپت خالص برای دریافت مقادیر وارد شده‌ی توسط کاربر می‌توان نوشت:
    const username = document.getElementById("username").value;
    اما در React و کدهای یک کامپوننت، نباید ارجاع مستقیمی را به شیء document و DOM اصلی مرورگر داشته باشیم. در برنامه‌های React هیچگاه نباید با شیء document کار کرد؛ چون کل فلسفه‌ی آن ایجاد یک abstraction بر فراز DOM اصلی مرورگر است که به آن DOM مجازی گفته می‌شود. به این ترتیب مدیریت برنامه و همچنین آزمون نویسی برای آن نیز ساده‌تر می‌شود. اما اگر واقعا نیاز به دسترسی به یک المان DOM در React وجود داشت، چه باید کرد؟
    برای دسترسی به یک المان DOM در React، باید یک reference را به آن نسبت داد. برای این منظور یک خاصیت جدید را در سطح کلاس کامپوننت، ایجاد کرده و آن‌را با React.RefObject، مقدار دهی اولیه می‌کنیم:
    class LoginForm extends Component {
      username = React.createRef();
    سپس ویژگی ref المان مدنظر را به این RefObject تنظیم می‌کنیم:
    <input
      ref={this.username}
      id="username"
      type="text"
      className="form-control"
    />
    اکنون زمان submit فرم، اگر نیاز به مقدار username وجود داشت، می‌توان توسط خاصیت ارجاعی username تعریف شده، به خاصیت current آن که DOM element مدنظر را بازگشت می‌دهد، دسترسی یافت و مانند مثال زیر، مقدار آن‌را مورد استفاده قرار داد:
      handleSubmit = e => {
        e.preventDefault();
    
        // call the server
        const username = this.username.current.value;
        console.log("handleSubmit", username);
      };

    البته در حالت کلی باید استفاده‌ی از RefObjectها را به حداقل رساند (راه حل بهتری برای دریافت ورودی‌ها وجود دارد) و جاهائی از آن‌ها استفاده کرد که واقعا راه حل دیگری وجود ندارد؛ مانند تنظیم focus بر روی یک المان DOM. در این حالت حتما باید ارجاعی را از آن المان DOM در دسترس داشت و یا برای پویانمایی (animation) نیز مجبور به استفاده‌ی از RefObjectها هستیم.
    برای نمونه روش تنظیم focus بر روی یک فیلد ورودی توسط RefObjectها به صورت زیر است:
    class LoginForm extends Component {
      username = React.createRef();
    
      componentDidMount = () => {
        this.username.current.focus();
      };
    در life-cycle hook ای به نام componentDidMount که پس از رندر کامپوننت در DOM فراخوانی می‌شود، می‌‌توان توسط RefObject تعریف شده، به شیء current که معادل DOM Element متناظر است، دسترسی یافت و سپس متد focus آن‌را فراخوانی کرد. در این حالت در اولین بار نمایش فرم، یک چنین تصویری حاصل می‌شود:


    البته روش بهتری نیز برای انجام اینکار وجود دارد. المان‌های JSX دارای ویژگی autoFocus نیز هستند که دقیقا همین کار را انجام می‌دهد:
    <input
      autoFocus
      ref={this.username}
      id="username"
      type="text"
      className="form-control"
    />
    برای آزمایش آن، قطعه کد componentDidMount را کامنت کرده و برنامه را اجرا کنید.


    تبدیل المان‌های فرم‌ها به Controlled elements

    در بسیاری از اوقات، فرم‌های ما state خود را از سرور دریافت می‌کنند. فرض کنید که در حال ایجاد یک فرم ثبت اطلاعات فیلم‌ها هستیم. در این حالت باید بر اساس id فیلم، اطلاعات آن را از سرور دریافت و در state ذخیره کرد؛ سپس فیلدهای فرم را بر اساس آن مقدار دهی اولیه کرد. برای نمونه در فرم لاگین می‌توان state را با شیء account، به صورت زیر مقدار دهی اولیه کرد:
    class LoginForm extends Component {
      state = {
        account: { username: "", password: "" }
      };
    تا اینجا فیلدهای فرم لاگین، از این state مطلع نبوده و تغییرات داده‌های ورودی در آن‌ها، به شیء account منعکس نمی‌شوند. علت اصلی هم اینجا است که هر کدام از فیلدهای ورودی در React، دارای state خاص خود بوده و مستقل از state کامپوننت جاری هستند. برای رفع این مشکل باید آن‌ها را تبدیل به controlled element هایی کرد که دارای state خاص خود نبوده، تمام اطلاعات مورد نیاز خود را از طریق props دریافت می‌کنند و تغییرات در داده‌های خود را از طریق صدور رخ‌دادهایی اطلاع رسانی می‌کنند. برای اینکار باید مراحل زیر طی شوند:
    ابتدا ویژگی value فیلد برای مثال username را به خاصیت username شیء account موجود در state متصل می‌کنیم:
    <input 
      value={this.state.account.username}
    به این ترتیب دیگر این المان، state خاص خود را نداشته و از طریق props، مقادیر خود را دریافت می‌کند. تا اینجا username، به رشته‌ی خالی دریافتی از شیء state و خاصیت account آن، به صورت یک طرفه متصل شده‌است. یعنی زمانیکه فرم نمایش داده می‌شود، دارای یک مقدار خالی است. برای اینکه تغییرات رخ‌داده‌ی در این المان را به state منعکس کرد، باید رخ‌داد change آن‌را مدیریت نمود. به این ترتیب زمانیکه کاربری اطلاعاتی را در اینجا وارد می‌کند، رخ‌داد change صادر شده و پس از آن می‌توان اطلاعات وارد شده را دریافت و state را به روز رسانی کرد. به روز رسانی state نیز سبب رندر مجدد فرم می‌شود. بنابراین فیلدهای ورودی، با اطلاعات state جدید، به روز رسانی و رندر می‌شوند. به همین جهت ابتدا رویداد onChange را به فیلد username اضافه کرده:
    <input 
      value={this.state.account.username}
      onChange={this.handleChange}
    و متد مدیریت کننده‌ی آن‌را به صورت زیر تعریف می‌کنیم:
      handleChange = e => {
        const account = { ...this.state.account }; //cloning an object
        account.username = e.currentTarget.value;
        this.setState({ account });
      };
    در اینجا، هدف به روز رسانی this.state.account، بر اساس رخ‌داد رسیده (پارامتر e) است و چون نمی‌توان state را مستقیما به روز رسانی کرد، ابتدا یک clone از آن را تهیه می‌کنیم. سپس توسط e.currentTarget به المان در حال به روز رسانی دسترسی یافته و مقدار آن‌را به مقدار خاصیت username انتساب می‌دهیم. در آخر state را بر اساس این تغییرات، به روز رسانی می‌کنیم. این انعکاس در state را توسط افزونه‌ی react developer tools هم می‌توان مشاهده کرد:



    مدیریت دریافت اطلاعات چندین فیلد ورودی

    تا اینجا موفق شدیم اطلاعات state را به تغییرات فیلد username در فرم لاگین متصل کنیم؛ اما فیلد password را چگونه باید مدیریت کرد؟ برای اینکه تمام این مراحل را مجددا تکرار نکنیم، می‌توان از مقدار دهی پویای خواص در جاوا اسکریپت که توسط [] انجام می‌شود استفاده کرد:
      handleChange = e => {
        const account = { ...this.state.account }; //cloning an object
        account[e.currentTarget.name] = e.currentTarget.value;
        this.setState({ account });
      };
    البته برای اینکه این قطعه کد کار کند، نیاز است ویژگی name فیلدهای ورودی را نیز تنظیم کرد تا e.currentTarget.name، به نام یکی از خواص شیء account تعریف شده‌ی در state اشاره کند. برای نمونه فیلد کلمه‌ی عبور، ابتدا دارای ویژگی value متصل به خاصیت password شیء account موجود در state می‌شود. سپس تغییرات آن توسط رویداد onChange، به متد handleChange منتقل شده و خاصیت name آن نیز مقدار دهی شده‌است تا مقدار دهی پویای خواص، در این متد میسر شود:
    <input
      id="password"
      name="password"
      value={this.state.account.password}
      onChange={this.handleChange}
      type="password"
      className="form-control"
    />
    که در نهایت سبب مقدار دهی صحیح state، با هر دو فیلد تغییر یافته می‌شود:


    یک نکته: می‌توان توسط Object Destructuring، تکرار e.currentTarget را حذف کرد:
      handleChange = ({ currentTarget: input }) => {
        const account = { ...this.state.account }; //cloning an object
        account[input.name] = input.value;
        this.setState({ account });
      };
    ما از شیء e دریافتی، تنها به خاصیت currentTarget آن نیاز داریم. بنابراین آن‌را از طریق Object Destructuring در همان پارامتر ورودی متد جاری دریافت کرده و سپس آن‌را به نام input، تغییر نام می‌دهیم.


    آشنایی با خطاهای متداول دریافتی در حین کار با فرم‌ها

    فرض کنید خاصیت username را از شیء account موجود در state حذف کرده‌ایم. در زمان نمایش ابتدایی فرم، خطایی را دریافت نخواهیم کرد، اما اگر اطلاعاتی را در آن وارد کنیم، بلافاصله در کنسول توسعه دهندگان مرورگر چنین اخطاری ظاهر می‌شود:
    Warning: A component is changing an uncontrolled input of type text to be controlled.
    Input elements should not switch from uncontrolled to controlled (or vice versa).
    Decide between using a controlled or uncontrolled input element for the lifetime of the component.
    More info: https://fb.me/react-controlled-components
    چون خاصیت username را حذف کرده‌ایم، اینبار که در textbox مقداری را وارد می‌کنیم، سبب انتساب undefined و یا null به مقدار المان خواهد شد. در این حالت React چنین المانی را به صورت controlled element درنظر نمی‌گیرد و دارای state خاص خودش خواهد بود. به همین جهت عنوان می‌کند که بین یک المان کنترل شده و نشده، یکی را انتخاب کنید.
    دقیقا چنین اخطاری را با ورود null/undefined بجای "" در حین مقدار دهی اولیه‌ی username در شیء account نیز دریافت خواهیم کرد:
    Warning: `value` prop on `input` should not be null.
    Consider using an empty string to clear the component or `undefined` for uncontrolled components.
    بنابراین به عنوان یک قاعده در فرم‌های React، المان‌های یک فرم را باید توسط یک "" مقدار دهی اولیه کرد و یا با مقداری که از سمت سرور دریافت می‌شود.


    ایجاد یک کامپوننت ورود اطلاعات با قابلیت استفاده‌ی مجدد

    هر چند در پیاده سازی فعلی سعی کردیم با بکارگیری مقداردهی پویای خواص اشیاء، تکرار کدها را کاهش دهیم، اما باز هم به ازای هر فیلد ورودی باید این مسایل تکرار شوند:
    - ایجاد یک div با کلاس‌های بوت استرپی.
    - ایجاد label و همچنین فیلد ورودی.
    - در اینجا مقدار htmlFor باید با مقدار id فیلد ورودی یکی باشد.
    - مقدار دهی ویژگی‌های value و onChange نیز باید تکرار شوند.

    بنابراین بهتر است این تعاریف را استخراج و به یک کامپوننت با قابلیت استفاده‌ی مجدد منتقل کرد. به همین جهت فایل جدید src\components\common\input.jsx را در پوشه‌ی common ایجاد کرده و سپس توسط میانبرهای imrc و sfc، این کامپوننت تابعی بدون حالت را تکمیل می‌کنیم:
    import React from "react";
    
    const Input = ({ name, label, value, onChange }) => {
      return (
        <div className="form-group">
          <label htmlFor={name}>{label}</label>
          <input
            value={value}
            onChange={onChange}
            id={name}
            name={name}
            type="text"
            className="form-control"
          />
        </div>
      );
    };
    
    export default Input;
    در اینجا کل تگ div مرتبط با username را از کامپوننت فرم لاگین cut کرده و در اینجا در قسمت return، قرار داده‌ایم. سپس شروع به تبدیل مقادیر قبلی به مقادیری که قرار است از props تامین شوند، کرده‌ایم. یا می‌توان props را به عنوان آرگومان این متد تعریف کرد و یا می‌توان توسط Object Destructuring، خواصی را که از props نیاز داریم، در پارامتر متد Input ذکر کنیم که این روش چون به نوعی اینترفیس کامپوننت را نیز مشخص می‌کند و همچنین کدهای تکراری دسترسی به props را به حداقل می‌رساند، تمیزتر و با قابلیت نگهداری بالاتری است. برای مثال هر جائیکه نام username استفاده شده بود، با خاصیت name جایگزین شده و بجای برچسب از label، بجای مقدار username از متغیر value و بجای رخ‌داد تعریف شده نیز onChange قرار گرفته‌است.

    سپس به کامپوننت فرم لاگین بازگشته و ابتدا آن‌را import می‌کنیم:
    import Input from "./common/input";
    اکنون متد رندر ماژول src\components\loginForm.jsx، به صورت زیر با درج دو Input، خلاصه می‌شود که دیگر در آن خبری از تگ‌ها و کدهای تکراری نیست:
      render() {
        const { account } = this.state;
        return (
          <form onSubmit={this.handleSubmit}>
            <Input
              name="username"
              label="Username"
              value={account.username}
              onChange={this.handleChange}
            />
            <Input
              name="password"
              label="Password"
              value={account.password}
              onChange={this.handleChange}
            />
            <button className="btn btn-primary">Login</button>
          </form>
        );


    کدهای کامل این قسمت را از اینجا می‌توانید دریافت کنید:  sample-18.zip
    مطالب
    تخته وایت برد آنلاین توسط SignalR
    همانطور که در دوره SignalR سایت نیز مطرح شده‌است : "یکی از کاربردهای جالب SignalR می‌تواند به روز رسانی مداوم صفحه نمایش کاربران، توسط اطلاعات ارسالی از طرف سرور باشد." در ادامه میخواهیم به طراحی یک "تخته وایت برد" آنلاین بپردازیم.
    در این پروژه برای ترسم خطوط بر روی صفحه از Canvas در  HTML5 استفاده میشود.

    پیشنیازها :
    پیشنیازهای این مطلب با مطلب «مثال - نمایش درصد پیشرفت عملیات توسط SignalR» یکی است. برای مثال، نحوه دریافت وابستگی‌ها، تنظیمات فایل global.asax و افزودن اسکریپت‌ها، تفاوتی با مقاله‌ی ذکر شده ندارد و تنها تعدادی اسکریپت و CSS جهت زیبایی کار افزوده شده است :
    <link href="Styles/bootstrap.min.css" rel="stylesheet" />
    
    //برای نمایش و استفاده از جعبه‌ی رنگ در بوت استراپ
    <link href="Styles/bootstrap-colorpalette.css" rel="stylesheet" />
    <script src="Scripts/jquery-1.8.2.js"></script>
    <script type="text/javascript" src='Scripts/jquery.signalR-1.1.3.js'></script>
    <script src="Scripts/bootstrap.min.js"></script>
    <script src="Scripts/bootstrap-colorpalette.js"></script>
    <script type="text/javascript" src='<%: ResolveClientUrl("~/signalr/hubs") %>'></script>
    
    //حاوی متدهایی برای رسم خط با استفاده از HTML5
    <script src="Scripts/draw.js" type="text/javascript"></script>
    
    //تعریف کلاینت‌های متصل به هاب
    <script src="Scripts/Whiteboard.js"></script>

      تعریف کلاس Hub برنامه :

    using Microsoft.AspNet.SignalR;
    using Microsoft.AspNet.SignalR.Hubs;
    using OnlineSignalRWhiteboard.Model;
    
    namespace OnlineSignalRWhiteboard.Hubs
    {
        [HubName("onWhiteboard")]
        public class Whiteboard : Hub
        {
            public void OnDrawPen(Point prev, Point current, string color, int width)
            {
                Clients.All.drawPen(prev, current, color, width);
            }
    
            public void ClearBoard()
            {
                Clients.All.clear();
            }
    
        }
    }
    در ابتدا توسط ویژگی HubName یک نام مشخص برای هاب خود انتخاب کرده ایم. سپس متدی به نام OnDrawPen تعریف شده است که پس از فراخوانی از سمت کلاینت، مقادیر دریافتی خود را با صدا زدن متدی در سمت کلاینت به نام drawPen  ، به تمام کلاینت‌ها ارسال و نقاط مربوطه در سمت کلاینت، بر روی صفحه رسم میشوند.
    متد دیگری به نام ClearBoard نیز تعریف شده است که روال رخدادگردان clear در سمت کلاینت را فراخوانی میکند و باعث پاک شدن صفحه نمایش میشود.

    کدهای کلاینت‌های متصل به هاب در فایل Whiteboard.js 
     :
    $(function () {
        $.connection.hub.logging = true;
        var whiteboard = $.connection.onWhiteboard;
    
        $("#clear").click(function () {
            //پاک کردن صفحه نمایش
            whiteboard.server.clearBoard();
        });
    
        var color = function (colors) {
            //تغییر رنگ قلم
            draw.colour = colors;
        };
    
        $(".size").click(function () {
            //تغییر سایز قلم
            draw.lineWidth = $(this).height();
            $('#size').css('height', $(this).height());
        });
    
        $.connection.hub.start().done(function () {
        })
        .fail(function () {
            alert("Could not Connect!");
         });
    
        draw.onDraw = function (prev, current, color, width) {
            //ارسال پارامترها به سمت سرور تا سرور بتواند داده‌ها را به سمت سایر کلاینت‌ها نیز ارسال کند
            whiteboard.server.onDrawPen(prev, current, color, width);
        };
    
        whiteboard.client.drawPen = function (prev, current, color, width) {
            draw.drawPen(prev, current, color, width);
        };
    
        whiteboard.client.clear = function () {
            draw.clear();
        };
    
        $('#colorpalette3').colorPalette()
          .on('selectColor', function (e) {
              $('#color').css('background-color', e.color);
              color(e.color);
          });
    
        var canvas = document.getElementById('draw');
        //تغییر سایر کانواس متناسب با پنجره‌ی مرورگر
        window.addEventListener('resize', resizeCanvas, false);
    
        function resizeCanvas() {
            canvas.width = window.innerWidth - 20;
            canvas.height = window.innerHeight - 70;
        }
        resizeCanvas();
    
    });
    در این قطعه کد ابتدا ارجاعی به هاب مشخص شده است و سپس روال‌های رخداد گردانی مانند whiteboard.client.drawPen و whiteboard.client.clear تعریف شده اند که به ترتیب متصل هستند به فراخوانی های Clients.All.drawPen و  Clients.All.clear در سمت سرور.
    همچنین یک متد به نام draw.onDraw تعریف شده است که وظیفه‌ی آن ارسال اطاعاتی نظیر موقعیت موس در صفحه، رنگ، اندازه و ...  به سمت متدی به نام onDrawPen در هاب است.

    کدهای کامل سمت کلاینت :
    <!DOCTYPE html>
    
    <html xmlns="http://www.w3.org/1999/xhtml">
    <head runat="server">
        <title></title>
        <link href="Styles/bootstrap.min.css" rel="stylesheet" />
        <link href="Styles/bootstrap-colorpalette.css" rel="stylesheet" />
        <script src="Scripts/jquery-1.8.2.js"></script>
        <script type="text/javascript" src='Scripts/jquery.signalR-1.1.3.js'></script>
        <script src="Scripts/bootstrap.min.js"></script>
        <script src="Scripts/bootstrap-colorpalette.js"></script>
        <script type="text/javascript" src='<%: ResolveClientUrl("~/signalr/hubs") %>'></script>
        <script src="Scripts/draw.js" type="text/javascript"></script>
        <script src="Scripts/Whiteboard.js"></script>
    </head>
    <body>
        <form id="form1" runat="server">
            <div>
                <div style="position: static;">
                  <div>
                    <div>
                      <a data-toggle="collapse" data-target=".navbar-inverse-collapse">
                        <span></span>
                        <span></span>
                        <span></span>
                      </a>
                      <a href="#">تخته وایت برد آنلاین توسط SignalR و HTML5</a>
                      <div>
                        <ul>
                          <li>
                              <div>
                                  <a id="selected-color2" data-toggle="dropdown">
                                      <div style="width: 20px; height: 20px; background: black" id="color">
                                      </div>
                                  </a>
                                  <ul style="width:293px;">
                                    <li style="display:inline-block;">
                                      <div>رنگ پس زمینه</div>
                                      <div id="colorpalette3"></div>
                                    </li>
                                  </ul>
                              </div>
                          </li>
                          <li></li>
                          <li>
                              <div>
                                  <a data-toggle="dropdown" style="width: 120px; height: 20px" href="#">
                                    <hr style="width: 120px; height: 1px; background-color: black;margin: 0px; display: table-cell" id="size">
                                  </a>
                                  <ul style="width: 120px">
                                    <li><hr style="width: 140px; height: 1px; background-color: black"></li>
                                      <li></li>
                                    <li><hr style="width: 140px; height: 3px; background-color: black"></li>
                                      <li></li>
                                    <li><hr style="width: 140px; height: 7px; background-color: black"></li>
                                      <li></li>
                                    <li><hr style="width: 140px; height: 10px; background-color: black"></li>
                                      <li></li>
                                    <li><hr style="width: 140px; height: 20px; background-color: black"></li>
                                  </ul>
                                </div>
                          </li>
                          <li></li>
                          <li>
                              <div>
                                <a href="#" id="clear"><i></i>جدید</a>
                              </div>
                          </li>
                        </ul>
                      </div><!-- /.nav-collapse -->
                    </div>
                  </div><!-- /navbar-inner -->
                </div>
            </div>
            
            <div>
                <div>
                     <canvas id="draw" style="cursor: crosshair;border: 1px solid black;"></canvas>
                </div>
           </div>
        </form>
    </body>
    </html>
    در ابتدا یک دکمه برای تغییر رنگ قلم و یک لیست بازشو برای تعیین اندازه‌ی قلم و همچنین یک دکمه برای پاک کردن صفحه نمایش قرار داده شده است. سپس یک Canvas به نام draw ایجاد کرده ایم که بتوانیم خطوط خود را بر روی آن رسم کنیم.

    مشاهده نسخه نمایشی 
    کدهای کامل این مثال را میتوانید از اینجا دانلود کنید : OnlineSignalRWhiteboard.zip  
    و همچنین میتوانید نمونه‌ی بهینه شده‌ی آن را نیز از این قسمت دانلود کنید :  OnlineCustomSignalRWhiteboard.zip  
    مطالب
    نگاهی به اجزای تعاملی Twitter Bootstrap
    پس از آشنایی با طرحبندی صفحات و امکانات متداول Twitter Bootstrap، در این قسمت به کامپوننت‌ها و اجزای تعاملی آن مانند منوها، برگه‌ها و امثال آن خواهیم پرداخت.

    منوهای پایین افتادنی (Dropdown menus) در Twitter Bootstrap

    برای کار با منوها، حداقل نیازهایی که باید به صفحه اضافه شوند، فایل css مرتبط با Twitter Bootstrap، فایل اسکریپت‌های جی‌کوئری و فایل bootstrap.min.js است.
    در ادامه، ساختار متداول یک منوی نمونه ایجاد شده را ملاحظه می‌کنید:
        <div class="container-fluid">
            <div class="row-fluid">
                <div class="span12">
                    <div class="dropdown">
                        <a class="dropdown-toggle" data-toggle="dropdown" href="#">منوی پایین افتادنی 
                        <span  class="caret"></span></a>
                        <ul class="dropdown-menu">
                            <li><a href="http://www.site1.com">site1</a></li>
                            <li><a href="http://www.site2.com">site2</a></li>
                            <li><a href="http://www.site3.com">site3</a></li>
                            <li class="divider"></li>
                            <li class="dropdown-submenu"><a href="#">سایر موارد</a>
                                <ul class="dropdown-menu">
                                    <li><a href="http://www.site4.com">site4</a></li>
                                    <li><a href="http://www.site5.com">site5</a></li>
                                </ul>
                            </li>
                        </ul>
                    </div>
                </div>
            </div>
        </div>

    در اینجا به یک div، کلاس dropdown نسبت داده شده است؛ از این جهت که می‌توان این منو را به انواع و اقسام المان‌های صفحه نیز نسبت داد. به این ترتیب، منو از محل قرارگیری این المان باز خواهد شد. در ادامه روال متداول تعریف منو را در Bootstrap، ملاحظه می‌کنید. کلاس dropdown-toggle سبب خواهد شد تا با کلیک ماوس بر روی لینک، منو ظاهر شود. کلاس caret نیز سبب نمایش مثلث رو به پایین، جهت مشخص سازی وجود منو در این محل خاص، تعریف شده است.
    المان‌های منو، توسط لیست‌ها تعریف می‌شوند. این لیست باید با کلاس dropdown-menu شروع شود. سپس هر عضو این لیست، یک آیتم منو را تشکیل خوهد داد. اگر نیاز به خط جدا کننده‌ای در این میان باشد، باید از کلاس divider استفاده کرد.
    در اینجا همچنین امکان تعریف منوهای تو در تو نیز وجود دارد. برای اینکار تنها کافی است یک لیست دیگر را در این میان اضافه کرد. کلاس dropdown-submenu به عضو لیست دربرگیرنده زیر منو، اضافه می‌شود. زیر منو نیز دارای کلاس dropdown-menu خواهد بود.

    چند نکته تکمیلی در مورد منوها:
    - همانطور که در قسمت بررسی دکمه‌های Bootstrap نیز عنوان شد، برای تبدیل یک المان به دکمه فقط کافی است کلاس btn را به آن انتساب دهیم. در اینجا نیز می‌توان کلاس لینک منوی پایین افتادنی را به صورت btn btn-primary dropdown-toggle تعریف کرد تا ظاهر آن تبدیل به یک دکمه Bootstrap شود.
    - در اولین div مثال فوق، کلاس dropdown ذکر شده است. می‌توان این کلاس را به dropup تغییر داد تا نحوه باز شدن منوی تعریف شده رو به بالا باشد؛ بجای رو به پایین.
    - برای ساخت split button که تشکیل شده است از دو دکمه اصلی و دکمه کوچکی در کنار آن که یک زیرمنو را نمایش می‌دهد، باید به نحو زیر عمل کرد:
        <div class="container-fluid">
            <div class="row-fluid">
                <div class="span12">
                    <div class="btn-group">
                        <a class="btn btn-primary" href="#">Split button</a> <a class="btn btn-primary dropdown-toggle"
                            data-toggle="dropdown" href="#"><span class="caret"></span></a>
                        <ul class="dropdown-menu">
                            <li><a href="http://www.site1.com">site1</a></li>
                            <li><a href="http://www.site2.com">site2</a></li>
                        </ul>
                    </div>
                </div>
            </div>
        </div>
    تنها نکته‌ای که در اینجا نسبت به حالت قبل اضافه شده است، استفاده از کلاس btn-group است. این کلاس سبب می‌شود تا دکمه‌های تعریف شده در کنار هم به شکل یک toolbar مرتب شوند.



    تعریف برگه‌ها در Twitter Bootstrap

    برای تعریف منوهای راهبری سایت به شکل برگه، کار با استفاده از کلاس nav و تعریف یک لیست ساده شروع می‌شود:
        <div class="container">
            <div class="row">
                <h2>
                    Navigational menus
                </h2>
                <div class="span3">
                    <ul class="nav nav-tabs">
                        <li class="nav-header">nav nav-tabs</li>
                        <li class="active"><a href="#">Home</a></li>
                        <li><a href="#">Contact</a></li>
                        <li><a href="#">Login</a></li>
                    </ul>
                </div>
                <div class="span3">
                    <ul class="nav nav-tabs nav-stacked">
                        <li class="nav-header">nav nav-tabs nav-stacked</li>
                        <li class="active"><a href="#">Home</a></li>
                        <li><a href="#">Contact</a></li>
                        <li><a href="#">Login</a></li>
                    </ul>
                </div>
                <div class="span3">
                    <ul class="nav nav-pills">
                        <li class="nav-header">nav nav-pills</li>
                        <li class="active"><a href="#">Home</a></li>
                        <li><a href="#">Contact</a></li>
                        <li><a href="#">Login</a></li>
                    </ul>
                </div>
                <div class="span3">
                    <ul class="nav nav-list">
                        <li class="nav-header">nav nav-list</li>
                        <li class="active"><a href="#">Home</a></li>
                        <li><a href="#">Contact</a></li>
                        <li><a href="#">Login</a></li>
                        <li class="dropdown"><a class="dropdown-toggle" data-toggle="dropdown" href="#">Menu
                            ...<span class="caret"></span></a>
                            <ul class="dropdown-menu">
                                <li><a href="http://www.site1.com">site1</a></li>
                                <li><a href="http://www.site2.com">site2</a></li>
                            </ul>
                        </li>
                    </ul>
                </div>
            </div>
        </div>


    - اضافه شدن کلاس nav، بولت‌های لیست را حذف و ویژگی تغییر رنگ زمینه هر یک از عناصر لیست را با حرکت اشاره‌گر ماوس بر روی آن فعال می‌کند.
    - برای اعمال اشکال مختلف به کلاس nav، می‌توان از سایر کلاس‌های مرتبط استفاده کرد. اگر از nav-tabs استفاده شود، این لیست عمودی تبدیل به یک لیست افقی خواهد شد. اگر کلاس active به یکی از آیتم‌های آن نسبت داده شود (بیانگر صفحه جاری)، این گزینه حالت انتخاب شده را پیدا می‌کند.
    - در هر حالت، برای تبدیل چیدمان افقی به عمودی، از کلاس nav-stacked استفاده نمائید.
    - کلاس nav-pills، برای نمایش عنصر فعال، از یک مستطیل با گوشه‌های گرد استفاده می‌کند.
    - اگر می‌خواهید این گوشه‌ها گرد نباشند، از nav-list استفاده کنید.
    - در هر کدام از حالت‌ها می‌توان از یک گزینه اختیاری با کلاس nav-header برای نمایش متنی خاکستری بالای منوی راهبری استفاده کرد.
    - همانطور که در آخرین لیست اضافه شده در مثال فوق ملاحظه می‌کنید، امکان تعریف منوهای پایین افتادنی در اینجا نیز میسر است. در این حالت کلاس li تعریف شده باید dropdown تعریف شود. مابقی نکات آن مانند قبل است.


    تعریف یک Tab به همراه محتوای برگه‌های آن در داخل یک صفحه

    در حالت تعریف منوهای راهبری فوق، هر عنصر برگه‌های تعریف شده به یک صفحه جدید لینک شده بود. اگر نیاز باشد تا هر برگه، محتوایی داخل همان صفحه داشته باشد باید به نحو ذیل عمل کرد:
        <div class="container">
            <div class="row">
                <div class="span12">
                    <div class="tabbable">
                        <ul class="nav nav-tabs">
                            <li class="active"><a data-toggle="tab" href="#tab1">Home</a></li>
                            <li><a data-toggle="tab" href="#tab2">About</a></li>
                        </ul>
                        <div class="tab-content">
                            <div class="tab-pane active" id="tab1">
                                data ... data ...
                            </div>
                            <div class="tab-pane" id="tab2">
                                data2 ... data2 ...
                            </div>
                        </div>
                    </div>
                </div>
            </div>
        </div>

    در اینجا کار با کلاس tabbable شروع می‌شود. سپس از یک لیست با کلاس nav nav-tabs برای تعریف سرتیترهای برگه‌ها استفاده می‌کنیم.
    اگر به مثال فوق دقت کنید، اینبار hrefهای تعریف شده به tab1 و tab2 اشاره می‌کنند. ایندو در حقیقت id محتوای متناظر هستند که در قسمت div ایی با کلاس tab-content به div هایی با id مساوی tab1 و tab2 انتساب داده شده‌اند.


    شیوه دوم تعریف منوی راهبری سایت به کمک Twitter Bootstrap

    اگر علاقمند باشید که منوی راهبری بالای سایت دارای یک حاشیه به همراه مسایل طراحی واکنش‌گرا باشد، می‌توان از کامپوننت navbar مجموعه Bootstrap استفاده کرد:
        <div class="container">
            <div class="row">
                <div class="span12">
                    <nav class="navbar navbar-inverse">
                        <div class="navbar-inner">
                            <a href="/" class="brand">نام سایت در اینجا</a> 
                            <a class="btn btn-navbar" data-toggle="collapse" data-target=".nav-collapse">
                                <span class="icon-bar"></span>
                                <span class="icon-bar"></span>
                                <span class="icon-bar"></span>
                            </a>
                            <div class="nav-collapse collapse">
                                <ul class="nav">
                                    <li><a href="/about">درباره</a></li>
                                    <li><a href="/contact">تماس</a></li>
                                    <li class="dropdown"><a class="dropdown-toggle" data-toggle="dropdown" href="#">Menu
                                        ...<span class="caret"></span></a>
                                        <ul class="dropdown-menu">
                                            <li><a href="http://www.site1.com">site1</a></li>
                                            <li><a href="http://www.site2.com">site2</a></li>
                                        </ul>
                                    </li>
                                </ul>
                                <form class="navbar-search pull-right input-append" action="/search">
                                <input class="span2" type="search" />
                                <button class="btn" type="button">
                                    جستجو</button>
                                </form>
                            </div>
                        </div>
                    </nav>
                </div>
            </div>
        </div>


    navbar نیز همانند بسیاری از کامپوننت‌های Bootstrap، بر اساس ul و liها کار می‌کند. در ابتدای کار، کلاس nav را به تگ ul انتساب می‌دهیم. این مورد با مباحث قسمت قبلی در مورد تدارک برگه‌ها، تفاوتی نمی‌کند. به این ترتیب بولت‌های لیست حذف شده و همچنین تغییر رنگ پس زمینه، با حرکت اشاره‌گر ماوس بر روی آیتم‌ها را سبب می‌شود. سپس کل این ul و li ها، داخل تگ nav قرار می‌گیرند با کلاسی مساوی navbar. تا اینجا لیست عمودی تبدیل به یک لیست افقی می‌شود.
    در ادامه برای اضافه کردن حاشیه‌ای به اطراف این منوی راهبری و تمایز آن از سایر قسمت‌های صفحه، یک div با کلاس navbar-inner را اضافه خواهیم کرد.
    عموما مرسوم است که در منوی راهبری اصلی سایت، نام سایت یا متنی مشخص نیز در قسمتی از این منو ظاهر شود. برای انجام اینکار، کافی است یک لینک را با کلاس brand اضافه کنیم.
    این موارد، اصول کلی تهیه یک منوی راهبری اصلی سایت را توسط Bootstrap بیان می‌کنند. در ادامه به چند نکته تکمیلی خواهیم پرداخت:
    - اگر به تگ nav، کلاس navbar-static-top را اضافه کنیم، فاصله بین منو و اطراف صفحه را حذف می‌کند.
    - اگر علاقمند هستیم که منوی بالای صفحه حتی با اسکرول به پایین محتوای نمایش داده شده، همواره نمایان باشد و بر روی محتوا قرار گیرد، می‌توان به تگ nav، کلاس navbar-fixed-top را اضافه کرد. یا اگر نیاز است این منو در پایین صفحه به صورت ثابت نمایش داده شود از navbar-fixed-bottom می‌شود استفاده کرد.
    در این حالت خاص ممکن است منو، قسمتی از ابتدای محتوای صفحه را مخفی کند و این محتوا زیر منو واقع شود. برای بهبود آن باید margin-top مرتبط با body را مقدار دهی کنید (حدود 40px کافی است).
    - استفاده از کلاس navbar-inverse (همانند مثال فوق)، سبب معکوس شدن رنگ زمینه منوی راهبری می‌شود.
    - همچنین در اینجا نیز می‌توان یک dropdown-menu را به نحوی که ملاحظه می‌کنید، به منوی اصلی سایت افزود.
    - امکان قرار دادن فرم‌های خاصی مانند فرم جستجو نیز در منوی راهبری وجود دارد. در این حالت از کلاس navbar-search مانند مثال فوق استفاده خواهد شد. کلاس pull-right این المان را به سمت راست صفحه (در اینجا به سمت چپ با توجه به RTL بودن قالب)، هدایت می‌کند و کلاس input-append سبب می‌شود تا دکمه و تکست باکس تعریف شده در یک سطر و کنار هم قرارگیرند تا ظاهر بهتری حاصل شود. در حالت کلی کلاس navbar-form نیز برای این نوع فرم‌ها درنظر گرفته شده است.
    - اگر دقت کرده باشید، کل محتوای منو، به همراه فرم تعریف شده، در یک div با کلاس nav-collapse قرار گرفته است. هدف از اینکار ناپدید شدن خودکار قسمت‌های طولانی یا نچندان مهم، در حین مرور سایت با اندازه‌های مرورگر کوچکتر مانند مرورگرهای موبایل است. همچنین اگر بخواهیم در این حالت که قسمت‌هایی ناپدید شده‌اند، یک دکمه با سه سطر که بیانگر منویی مخفی است را نمایش دهیم می‌توان از کلاس btn-navbar استفاده کرد؛ که نمونه‌ای از آن‌را در مثال فوق ملاحظه می‌کنید. این دکمه بر اساس data-target مشخص شده، سبب مخفی یا ظاهر شدن قسمتی از صفحه می‌گردد.




    تعیین موقعیت کاربر در صفحه به کمک breadcrumbs در Bootstrap

        <div class="container">
            <div class="row">
                <div class="span12">
                    <ul class="breadcrumb">
                        <li><a href="/level1">سطح یک</a> <span class="divider">/</span></li>
                        <li><a href="/level2">سطح دو</a> <span class="divider">/</span></li>
                        <li class="active">سطح سه</li>
                    </ul>
                </div>
            </div>
        </div>
    عموما مرسوم است در یک سایت چند سطحی، موقعیت جاری کاربر، به نحوی به او اعلام شود. یکی از این روش‌ها استفاده از breadcrumbs است که نمونه‌ای از آن‌را در مثال فوق ملاحظه می‌کنید.
    - در اینجا نیز برای تعریف breadcrumbs از ul و li استفاده شده است به همراه کلاس breadcrumb.
    - برای ایجاد فاصله و نمایش یک خط جداکننده، از کلاس divider کمک گرفته شده است.
    - صفحه جاری با کلاس active مشخص گردیده؛ بدون تعریف لینک.


    تعریف قسمت صفحه بندی یک لیست یا گرید به کمک Bootstrap

    <div class="container">
            <div class="row">
                <div class="span12">
                    <div class="pagination pagination-centered pagination-large">
                        <ul>
                            <li class="disabled"><a href="#">Prev</a></li>
                            <li class="active"><a href="/page/1">1</a></li>
                            <li><a href="/page/2">2</a></li>
                            <li><a href="/page/3">3</a></li>
                            <li><a href="/page/4">4</a></li>
                            <li><a href="#">Next</a></li>
                        </ul>
                    </div>
                </div>
            </div>
        </div>

    برای نمایش زیباتر قسمت صفحه بندی یک لیست یا گرید می‌توان از یک سری ul و li مزین شده با کلاس pagination استفاده کرد. کلاس pagination-centered، آن‌را در وسط صفحه قرار خواهد داد (حالت‌های چپ و راست نیز وجود دارند). کلاس pagination-large، این قسمت را اندکی بزرگتر از معمول نمایش می‌دهد.
    در اینجا کلاس disabled، سبب غیرفعال نمایش داده شدن لینک به یک صفحه و کلاس active، بیانگر نمایش صفحه جاری است.


    نمایش برچسب‌ها و اعلانات با زمینه‌های مختلف به کمک Bootstrap

    برای نمایش برچسب‌های کوتاه با زمینه‌ای متفاوت از کلاس label استفاده می‌شود. افزودن کلاس‌هایی مانند  label-success و امثال آن که در مثال ذیل ذکر شده‌اند، رنگ‌های متفاوتی را سبب خواهند شد. برای گرد کردن بیش از حد گوشه این برچسب‌ها از کلاس badge استفاده می‌شود.
        <div class="container">
            <div class="row">
                <div class="span12">
                    <section>
                        <ul class="unstyled">
                            <li><span class="label">پیش فرض</span></li>
                            <li><span class="label label-success">موفقیت آمیز</span></li>
                            <li><span class="label label-warning">اخطار</span></li>
                            <li><span class="label label-importatnt">مهم است</span></li>
                            <li><span class="label label-info">اطلاعات</span></li>
                            <li><span class="label label-inverse">رنگ معکوس</span></li>
                        </ul>
                    </section>
                    <section>
                        <ul class="unstyled">
                            <li><span class="badge">پیش فرض</span></li>
                            <li><span class="badge badge-success">موفقیت آمیز</span></li>
                            <li><span class="badge badge-warning">اخطار</span></li>
                            <li><span class="badge badge-importatnt">مهم است</span></li>
                            <li><span class="badge badge-info">اطلاعات</span></li>
                            <li><span class="badge badge-inverse">رنگ معکوس</span></li>
                        </ul>
                    </section>
                    <div>
                        <div class="alert">
                            <button class="close" data-dismiss="alert">&times;</button>
                            نمایش اعلانات
                        </div>
                        <div class="alert alert-danger">
                            <button class="close" data-dismiss="alert">&times;</button>
                            نمایش اعلانات
                        </div>
                        <div class="alert alert-info">
                            <button class="close" data-dismiss="alert">&times;</button>
                            نمایش اعلانات
                        </div>
                        <div class="alert alert-success">
                            <button class="close" data-dismiss="alert">&times;</button>
                            نمایش اعلانات
                        </div>
                        <div class="alert alert-error">
                            <button class="close" data-dismiss="alert">&times;</button>
                            نمایش اعلانات
                        </div>
                    </div>
                </div>
            </div>
        </div>

    همانطور که ملاحظه می‌کنید برای نمایش اعلانی به کاربر می‌توان از کلاس alert استفاده کرد. افزودن کلاس‌هایی مانند alert-danger، رنگ‌های متفاوتی را ارائه خواهند داد و اگر علاقمندید که کاربر بتواند این اخطار را با کلیک بر روی دکمه ضربدر کنار آن حذف کند، از یک دکمه با کلاس close و data-dismiss مساوی alert استفاده کنید. این اطلاعات به صورت خودکار توسط اسکریپت‌های bootstrap پردازش می‌شوند.
    مطالب
    شروع به کار با AngularJS 2.0 و TypeScript - قسمت پنجم - بررسی چرخه‌ی حیات کامپوننت‌ها
    در قسمت قبل، نگاهی داشتیم به 4 نوع مختلف data binding در AngularJS 2.0. در قسمت جاری می‌خواهیم کیفیت کدهای کامپوننت لیست محصولات را با strong typing بهبود بخشیده و همچنین چرخه‌ی حیات کامپوننت‌ها را به همراه ایجاد custom pipes بررسی کنیم.


    افزودن strong typing به کامپوننت نمایش لیست محصولات

    یکی از مزایای کار با TypeScript امکان انتساب نوع‌های مشخص یا سفارشی، به متغیرها و اشیاء تعریف شده‌است. برای مثال تاکنون هر خاصیت تعریف شده‌ای دارای نوع است. اما هنوز نوعی را برای آرایه‌ی محصولات تعریف نکرده‌ایم و نوع آن، نوع پیش فرض any است که برخلاف رویه‌ی متداول کار با TypeScript است.
    برای تعریف نوع‌های سفارشی می‌توان از اینترفیس‌های TypeScript استفاده کرد. یک اینترفیس، قراردادی است که نحوه‌ی تعریف تعدادی خاصیت و متد به هم مرتبط را مشخص می‌کند. سپس کلاس‌های مختلف می‌توانند با پیاده سازی این اینترفیس، قرارداد تعریف شده‌ی در آن را عملی کنند. همچنین از اینترفیس‌ها می‌توان به عنوان یک data type جدید نیز استفاده کرد. البته ES 5 و ES 6 از اینترفیس‌ها پشتیبانی نمی‌کنند و تعریف آن‌ها در TypeScript صرفا جهت کمک به کامپایلر، برای یافتن خطاها، پیش از اجرای برنامه است و به کدهای جاوا اسکریپتی معادلی ترجمه نمی‌شوند.

    در ادامه برای تکمیل مثال این سری، فایل جدید App\products\product.ts را به پروژه اضافه کنید؛ با این محتوا:
    export interface IProduct {
        productId: number;
        productName: string;
        productCode: string;
        releaseDate: string;
        price: number;
        description: string;
        starRating: number;
        imageUrl: string;
    }
    یک اینترفیس، با واژه‌ی کلیدی interface تعریف شده و سپس نام آن تعریف می‌شود. مرسوم است این نام با I شروع شود؛ هرچند الزامی نیست و در بسیاری از اینترفیس‌های AngularJS 2.0 از این روش نامگذاری استفاده نشده‌است.
    همچنین از آنجائیکه این اینترفیس را در یک فایل ts مجزا قرار داده‌ایم، برای اینکه بتوان از آن در سایر قسمت‌های برنامه استفاده کرد، نیاز است در ابتدای آن، واژه‌ی کلیدی export را نیز ذکر کرد.

    پس از تعریف این اینترفیس، برای استفاده از آن به عنوان یک data type جدید، ابتدا ماژول آن import خواهد شد و سپس از نام آن به عنوان نوع داده‌ی جدیدی، استفاده می‌شود. برای این منظور فایل product-list.component.ts را گشوده و تغییرات ذیل را به آن اعمال کنید:
    import { Component } from 'angular2/core';
    import { IProduct } from './product';
     
    @Component({
        selector: 'pm-products',
        templateUrl: 'app/products/product-list.component.html'
    })
    export class ProductListComponent {
        // as before ...
    
        products: IProduct[] = [
            // as before ...
        ]; 
    
        // as before ...
    }
    در اینجا ابتدا IProduct را import و سپس بجای any، نوع جدید IProduct را جهت مشخص سازی نوع آرایه‌ی products تعریف کرده‌ایم.
    مزیت اینکار این است که برای مثال در اینجا اگر در لیست اعضای آرایه‌ی products، نام خاصیتی اشتباه تایپ شده باشد یا حتی بجای عدد، از رشته استفاده شده باشد، بلافاصله در ادیتور مورد استفاده، خطای مرتبط گوشزد شده و همچنین این فایل دیگر کامپایل نخواهد شد. به علاوه اینبار برای تعریف خواص اعضای آرایه‌ی products، ادیتور مورد استفاده، intellisense را نیز دراختیار ما قرار می‌دهد و کاملا مشخص است که چه اعضایی مدنظر هستند و نوع آن‌ها چیست.



    مدیریت cssهای هر کامپوننت به صورت مجزا

    هنگام ساخت یک قالب یا template، در بسیاری از اوقات نیاز است css مرتبط با آن نیز، منحصر به همان قالب بوده و نشتی نداشته باشد. برای مثال زمانیکه یک کامپوننت را درون کامپوننتی دیگر قرار می‌دهیم، باید css آن نیز در دسترس قرار بگیرد و css فعلی کامپوننت دربرگیرنده را بازنویسی نکند. روش‌‌های مختلفی برای مدیریت این مساله وجود دارند:
    الف) تعریف شیوه نامه‌ها به صورت inline داخل خود قالب‌ها. این حالت، مشکلات نگهداری و استفاده‌ی مجدد را دارد.
    ب) تعریف شیوه نامه‌ها در یک فایل خارجی css و سپس لینک کردن آن به صفحه‌ای اصلی یا index.html
    در این حالت به ازای هر فایل، یکبار باید این تعریف در صفحه‌ای اصلی سایت صورت گیرد. همچنین این فایل‌ها می‌توانند مقادیر یکدیگر را بازنویسی کرده و بر روی هم تاثیر بگذارند.
    ج) تعریف شیوه نامه‌ها به همراه تعریف کامپوننت. این روشی است که توسط AngularJS توصیه شده‌است و نگهداری و مقیاس پذیری آن ساده‌تر است.
    تزئین کننده‌ی Component به همراه دو خاصیت دیگر به نام‌های styles و stylesUrl نیز می‌باشد.
    در حالت استفاده از خاصیت styles، شیوه‌نامه‌ی متناظر با کامپوننت، در همانجا به صورت inline تعریف می‌شود:
     @Component({
        //...
        styles: ['thead {color: blue;}']
    })
    همانطور که مشاهده می‌کنید، خاصیت styles به صورت یک آرایه تعریف شده‌است و امکان پذیرش چندین شیوه نامه‌ی جدا شده‌ی با کاما را دارد.
    روش بهتر، استفاده از خاصیت styleUrls است که در آن می‌توان مسیر یک یا چند فایل css را مشخص کرد:
     @Component({
         //...
         styleUrls: ['app/products/product-list.component.css']
    })
    این خاصیت نیز یک آرایه را می‌پذیرد و در اینجا می‌توان مسیر چندین فایل css را در صورت نیاز ذکر کرد.

    برای آزمایش آن فایل جدید product-list.component.css را به پوشه‌ی products مثال این سری اضافه کنید؛ با این محتوا:
    thead {
      color: #337AB7;
    }
    سپس لینک این فایل را به مجموعه خواص کامپوننت لیست محصولات (product-list.component.ts) اضافه می‌کنیم:
    @Component({
        selector: 'pm-products',
        templateUrl: 'app/products/product-list.component.html',
        styleUrls: ['app/products/product-list.component.css']
    })
    export class ProductListComponent {
       //...
    در این حالت اگر برنامه را اجرا کنید، رنگ سرستون‌های جدول محصولات به نحو ذیل تغییر کرده‌اند:


    یک نکته

    شیوه نامه‌ای که به این صورت توسط AngularJS 2.0 اضافه می‌شود، با سایر شیوه نامه‌های موجود تداخل نخواهد کرد. علت آن‌را در تصویر ذیل که با استفاده از developer tools مرورگرها قابل بررسی است، می‌توان مشاهده کرد:


    در اینجا AngularJS 2.0، با ایجاد ویژگی‌های سفارشی خودکار (attributes) میدان دید css را کنترل می‌کند. به این ترتیب شیوه نامه‌ی کامپوننت یک، که درون کامپوننت دو قرار گرفته‌است، نشتی نداشته و بر روی سایر قسمت‌های صفحه تاثیری نخواهد گذاشت؛ برخلاف شیوه نامه‌هایی که به صورت متداولی به صفحه‌ی اصلی سایت لینک شده‌‌اند.


    بررسی چرخه‌ی حیات کامپوننت‌ها

    هر کامپوننت دارای چرخه‌ی حیاتی است که توسط AngularJS 2.0 مدیریت می‌شود و شامل مراحلی مانند ایجاد، رندر، ایجاد و رندر فرزندان آن، پردازش تغییرات آن و در نهایت تخریب آن کامپوننت می‌شود. برای اینکه بتوان با برنامه نویسی به این مراحل چرخه‌ی حیات یک کامپوننت دسترسی یافت، تعدادی life cycle hook طراحی شده‌اند. سه مورد از مهم‌ترین life cycle hooks شامل موارد ذیل هستند:
    الف) OnInit: از این hook برای انجام کارهای آغازین یک کامپوننت مانند دریافت اطلاعات از سرور، استفاده می‌شود.
    ب) OnChanges: از آن جهت انجام اعمالی پس از تغییرات input properties استفاده می‌شود.
    خواص ورودی و همچنین کار با سرور را در قسمت‌های بعدی بررسی خواهیم کرد.
    ج) OnDestroy: از آن جهت پاکسازی منابع اختصاص داده شده استفاده می‌شود.

    برای استفاده‌ی از این hookها، نیاز است اینترفیس آن‌ها را پیاده سازی کنیم. از آنجائیکه AngularJS 2.0 نیز با TypeScript نوشته شده‌است، به همراه تعدادی اینترفیس از پیش تعریف شده می‌باشد. برای مثال به ازای هر life cycle hook، یک اینترفیس تعریف شده در آن وجود دارد. برای نمونه اینترفیس hook ایی به نام OnInit، دقیقا همان OnInit، نام دارد (و با I شروع نشده‌است):
     export class ProductListComponent implements OnInit {
    پس از ذکر implements OnInit در انتهای تعریف کلاس، اکنون باید ماژول مرتبط با آن نیز جهت شناسایی این اینترفیس import شود:
     import { Component, OnInit } from 'angular2/core';
    و دست آخر متد ngOnInit تعریف شده‌ی در این اینترفیس باید توسط کلاس پیاده سازی کننده‌ی آن تامین شود:
    ngOnInit(): void {
        console.log('In OnInit');
    }
    نام این متدها عموما شروع شده با ng و ختم شده به نام اینترفیس hook متناظر هستند؛ مانند ngOnInit فوق.

    به عنوان تمرین، فایل product-list.component.ts را گشوده و سه مرحله‌ی implements سپس import و در آخر تعریف متد ngOnInit فوق را به آن اضافه کنید.
    در ادامه برنامه را اجرا کرده و به کنسول developer tools مرورگر خود جهت مشاهده‌ی console.log فوق مراجعه کنید:



    ساخت یک Pipe سفارشی جهت فعال سازی textbox فیلتر کردن محصولات

    همانطور که در قسمت قبل نیز عنوان شد، کار pipes، تغییر اطلاعات حاصل از data binding، پیش از نمایش آن‌ها در رابط کاربری است و AngularJS 2.0 به همراه تعدادی pipe توکار است؛ مانند currency، percent و غیره. در ادامه قصد داریم یک pipe سفارشی را ایجاد کنیم تا بر روی حلقه‌ی ngFor* نمایش لیست محصولات تاثیرگذار شود و همچنین ورودی خود را از مقدار وارد شده‌ی توسط کاربر دریافت کند.
    برای این منظور، یک فایل جدید را به نام product-filter.pipe.ts به پوشه‌ی products اضافه کنید. سپس کدهای آن‌را به نحو ذیل تغییر دهید:
    import { PipeTransform, Pipe } from 'angular2/core';
    import { IProduct } from './product';
     
    @Pipe({
        name: 'productFilter'
    })
    export class ProductFilterPipe implements PipeTransform {
     
        transform(value: IProduct[], args: string[]): IProduct[] {
            let filter: string = args[0] ? args[0].toLocaleLowerCase() : null;
            return filter ? value.filter((product: IProduct) =>
                product.productName.toLocaleLowerCase().indexOf(filter) != -1) : value;
        }
    }
    برای تعریف یک pipe سفارشی جدید، کار با پیاده سازی اینترفیس PipeTransform شروع می‌شود. این اینترفیس دارای متدی است به نام transform که امضای آن‌را در کدهای فوق ملاحظه می‌کنید. کار آن اعمال تغییرات بر روی value دریافتی و سپس بازگشت آن است. بنابراین اولین پارامتر آن، مقادیر اصلی را که قرار است تغییر کنند، مشخص می‌کند. در اینجا نوع آن‌را از نوع اینترفیسی که در ابتدای بحث تعریف کردیم، تعیین کرده‌ایم. پارامتر دوم آن، لیست پارامترها و آرگومان‌های اختیاری این فیلتر را مشخص می‌کند.
    برای مثال در اینجا می‌خواهیم شرایط فیلتر محصولات وارد شده‌ی توسط کاربر را دریافت کنیم.
    خروجی این متد نیز از نوع آرایه‌ای از IProduct تعریف شده‌است؛ از این جهت که نتیجه نهایی فیلتر اطلاعات نیز آرایه‌ای از همین نوع است. کار این pipe پیاده سازی متد contains به صورت غیرحساس به کوچکی و بزرگی حروف است.
    سپس بلافاصله بالای نام این کلاس، از یک decorator جدید به نام Pipe استفاده شده‌است تا به AngularJS 2.0 اعلام شود، این کلاس، صرفا یک کلاس معمولی نیست و یک Pipe است.
    در ابتدای فایل هم importهای لازم جهت تعریف اینترفیس‌های مورد استفاده‌ی در این ماژول، ذکر شده‌اند.

    اگر دقت کنید، الگوی ایجاد یک pipe جدید، بسیار شبیه است به الگوی ایجاد یک کامپوننت و از این لحاظ سعی شده‌است طراحی یک دستی در سراسر این فریم ورک بکار گرفته شود.

    پس از تعریف این pipe سفارشی، برای استفاده‌ی از آن در یک template، به فایل product-list.component.html مراجعه کرده و سپس ngFor* آن‌را به نحو ذیل تغییر می‌دهیم:
     <tr *ngFor='#product of products | productFilter:listFilter'>
    همانطور که ملاحظه می‌کنید، نام این pipe جدید که در decorator مرتبط با آن، توسط خاصیت name مشخص گردیده‌است، ذکر شده‌است. پس از آن یک : قرار گرفته‌است که مشخص کننده‌ی پارامتر اول این pipe است که در اینجا خاصیت listFilter تعریف شده‌ی در قسمت قبل را به آن انتساب داده‌ایم.
    اگر از قسمت قبل به خاطر داشته باشید، این خاصیت را توسط two-way binding به روز می‌کنیم (اطلاعات وارد شده‌ی در textbox، بلافاصله به این خاصیت منعکس می‌شوند و برعکس):
     <input type='text'  [(ngModel)]='listFilter' />
    تا اینجا این pipe را در قالب لیست محصولات بکار بردیم؛ اما کامپوننت آن نمی‌داند که این pipe را باید از کجا تامین کند. به همین جهت فایل product-list.component.ts را گشوده و خاصیت pipes را به نحو ذیل مقدار دهی کنید:
    import { Component, OnInit } from 'angular2/core';
    import { IProduct } from './product';
    import { ProductFilterPipe } from './product-filter.pipe';
     
    @Component({
        selector: 'pm-products',
        templateUrl: 'app/products/product-list.component.html',
        styleUrls: ['app/products/product-list.component.css'],
        pipes: [ProductFilterPipe]
    })
    export class ProductListComponent implements OnInit {
       //...
    در اینجا دو کار صورت گرفته‌است. ابتدا افزودن pipe جدید ProductFilterPipe به لیست خاصیت pipes کامپوننت و سپس import ماژول آن درابتدای فایل.

    اکنون اگر برنامه را اجرا کنید، خروجی ذیل را مشاهده خواهید کرد:


    در اینجا چون مقدار فیلتر وارد شده‌ی پیش فرض، cart است، فقط ردیف Garden Cart نمایش داده شده‌است. اگر این مقدار را خالی کنیم، تمام ردیف‌ها نمایش داده می‌شوند و اگر برای مثال ham را جستجو کنیم، فقط ردیف Hammer نمایش داده می‌شود.


    کدهای کامل این قسمت را از اینجا می‌توانید دریافت کنید: MVC5Angular2.part5.zip


    خلاصه‌ی بحث

    - اینترفیس‌ها یکی از روش‌های بهبود strong typing برنامه‌های AngularJS 2.0 هستند.
    - جهت مدیریت بهتر شیوه‌نامه‌های هر کامپوننت بهتر است از روش styleUrls استفاده شود تا از نشتی‌های تعاریف شیوه‌نامه‌ها جلوگیری گردد.
    - از life cycle hooks برای مدیریت رخدادهای مرتبط با طول عمر یک کامپوننت استفاده می‌شود؛ برای مثال دریافت اطلاعات از سرور و یا پاکسازی منابع مصرفی.
    - تعریف یک pipe سفارشی با پیاده سازی اینترفیس PipeTransform انجام می‌شود. سپس نام این Pipe، به قالب مدنظر اضافه شده و در ادامه نیاز است کامپوننت استفاده کننده‌ی از این قالب را نیز از وجود این Pipe مطلع کرد.
    مطالب
    آشنایی با آزمایش واحد (unit testing) در دات نت، قسمت 4

    ادامه آشنایی با NUnit

    اگر قسمت سوم را دنبال کرده باشید احتمالا از تعداد مراحلی که باید در خارج از IDE صورت گیرد گلایه خواهید کرد (کامپایل کن، اجرا کن، اتچ کن، باز کن، ذخیره کن، اجرا کن و ...). خوشبختانه افزونه ReSharper این مراحل را بسیار ساده و مجتمع کرده است.
    این افزونه به صورت خودکار متدهای آزمایش واحد یک پروژه را تشخیص داده و آنها را با آیکون‌هایی ( Gutter icons ) متمایز مشخص می‌سازد (شکل زیر). پس از کلیک بر روی آن‌ها ، امکان اجرای آزمایش یا حتی دیباگ کردن سطر به سطر یک متد آزمایش واحد درون IDE‌ ویژوال استودیو وجود خواهد داشت.



    برای نمونه پس از اجرای آزمایش واحد قسمت قبل، نتیجه حاصل مانند شکل زیر خواهد بود:



    راه دیگر، استفاده از افزونه TestDriven.NET است که نحوه استفاده از آن‌را اینجا می‌توانید ملاحظه نمائید. به منوی جهنده کلیک راست بر روی یک صفحه، گزینه run tests را اضافه می‌کند و نتیجه حاصل را در پنجره output ویژوال استودیو نمایش می‌دهد.

    ساختار کلی یک کلاس آزمایش واحد مبتنی بر NUnit framework :

    using NUnit.Framework;
    using NUnit.Framework.SyntaxHelpers;

    namespace TestLibrary
    {
    [TestFixture]
    public class Test2
    {
    [SetUp]
    public void MyInit()
    {
    //کدی که در این قسمت قرار می‌گیرد پیش از اجرای هر متد تستی اجرا خواهد شد
    }

    [TearDown]
    public void MyClean()
    {
    //کدی که در این قسمت قرار می‌گیرد پس از اجرای هر متد تستی اجرا خواهد شد
    }

    [TestFixtureSetUp]
    public void MyTestFixtureSetUp()
    {
    // کدی که در اینجا قرار می‌گیرد در ابتدای بررسی آزمایش واحد و فقط یکبار اجرا می‌شود
    }

    [TestFixtureTearDown]
    public void MyTestFixtureTearDown()
    {
    // کدهای این قسمت در پایان کار یک کلاس آزمایش واحد اجرا خواهند شد
    }

    [Test]
    public void Test1()
    {
    //بدنه آزمایش واحد در اینجا قرار می‌گیرد
    Assert.That(2, Is.EqualTo(2));
    }
    }
    }

    شبیه به روال‌های رخداد گردان load و close یک فرم، یک کلاس آزمایش واحد NUnit نیز دارای ویژگی‌های TestFixtureSetUp و TestFixtureTearDown است که در ابتدا و انتهای آزمایش واحد اجرا خواهند شد (برای درک بهتر موضوع و دنبال کردن نحوه‌ی اجرای این روال‌ها، داخل این توابع break point قرار دهید و با استفاده از ReSharper ، آزمایش را در حالت دیباگ آغاز کنید)، یا SetUp و TearDown که در زمان آغاز و پایان بررسی هر متد آزمایش واحدی فراخوانی می‌شوند.
    همانطور که در قسمت قبل نیز ذکر شد، به امضاهای متدها و کلاس فوق دقت نمائید (عمومی ، void و بدون آرگومان ورودی).
    بهتر است از ویژگی‌های SetUp و TearDown با دقت استفاده نمود. عموما هدف از این روال‌ها ایجاد یک شیء و تخریب و پاک سازی آن است. حال اینکه این روال‌ها قبل و پس از اجرای هر متد آزمایش واحدی فراخوانی می‌شوند. بنابراین به این موضوع دقت داشته باشید.
    همچنین توصیه می‌شود که کلاس‌های آزمایش واحد را در اسمبلی دیگری مجزا از پروژه اصلی پیاده سازی کنید (برای مثال یک پروژه جدید از نوع class library)، زیرا این موارد مرتبط با بررسی کیفیت کدهای شما هستند که موضوع جداگانه‌ای نسبت به پروژه اصلی محسوب می‌گردد (نحوه پیاده سازی آن‌‌را در قسمت قبل ملاحظه نمودید). همچنین در یک پروژه تیمی این جدا سازی، مدیریت آزمایشات را ساده‌تر می‌سازد و بعلاوه سبب حجیم شدن بی‌مورد اسمبلی‌های اصلی محصول شما نیز نمی‌گردند.


    ادامه دارد...

    مطالب
    آموزش فایرباگ - #9 - Script Panel - Side Panels
    سه پنل Watch، Stack و Breakpoints پنل‌های جانبی پنل Script هستند که امکانات و اطلاعات مفیدی در ارتباط با قطعه کدی که در حال دیباگ آن هستیم ارائه می‌کنند.


    Watch
    این پنل متغییرها و تغییرات مقادیر آن‌ها در هنگام دیباگ را نمایش می‌دهد. بصورت پیشفرض متغییرهایی که در بلاک فعلی (که دیباگر در آن توقف کرده) ایجاد شده اند در این لیست قرار دارند. اما می‌توانید متغییرها و عبارات مورد نظر را در این قسمت وارد کنید. برای مثال می‌توانید مساوی بودن یک متغییر با یک مقدار را در این لیست وارد کنید و نتیجه را در هر اجرا مشاهده کنید. یا حتی یک متد را در این لیست وارد کنید تا در هر اجرا ، اجرا شود و نتیجه نمایش داده شود.
    برای اضافه کردن یک عبارت جدید به این لیست، بروی عبارت New watch expression... در بالای پنل کلیک کرده و عبارت مورد نظر را وارد کنید و در نهایت کلید Enter را بزنید.
    برای حذف عبارتی که وارد کردید، بروی علامت X کلیک کنید.
    توجه کنید که هر عبارتی که در این قسمت وارد می‌کنید در اجرای هر خط از برنامه در حالت دیباگ اجرا می‌شود و اگر عبارتی که وارد کرده اید مقادیر داخل برنامه را تغییر دهند، برنامه‌ی شما متفاوت از حالت عادی عمل خواهد کرد.


    Options Menu و Context Menu در این پنل همان موارد پنل DOM هستند و از آنجایی که همه‌ی این آیتم‌ها در این پنل به درستی عمل نمی‌کنند، توضیح آن را در مقاله‌ی مربوط به پنل DOM ارائه می‌کنیم.


    Stack

    در این پنل، زمانی که دیباگر متوقف شده است،لیستی از توابع که با فراخوانی یکدیگر به تابع جاری رسیده اند، نمایش داده می‌شوند. برای هر تابع هم پارامترها و مقادیر آن‌ها هنگام فراخوانی قابل مشاهده است. با کلیک بروی هر کدام از آن ها، خطی که تابع در آن قرار دارد نمایش داده می‌شود و همچنین می‌توانید مقادیر متغییرهای محلی آن تابع هنگامی که فرخوانی شده است را هم بررسی کنید. (جالب نیست؟)
    با راست کلیک کردن بروی نام این پنل یا باز کردن فلش آن، می‌توانید نمایش یا عدم نمایش این لیست در Toolbar را مشخص کنید.




    Breakpoints
    در این پنل لیست Break Point‌های اضافه شده نمایش داده می‌شود و می‌توانید آن هارا فعال، غیرفعال یا حذف نمایید.
    برای فعال، غیرفعال و حذف کردن یک یا همه‌ی Break Point‌ها بروی قسمت از پنل راست کلیک کرده و یکی از گزینه‌های مورد نظر را انتخاب کنید.

    مطالب
    استفاده از کتابخانه‌های ثالث جاوا اسکریپتی در برنامه‌های AngularJS 2.0
    هزاران کتابخانه‌ی جاوا اسکریپتی مستقل از AngularJS 2.0 وجود دارند که نیاز است بتوانیم از آن‌ها در برنامه‌های مختلفی استفاده کنیم. در این مطلب، دو روش بارگذاری آن‌ها را بررسی خواهیم کرد.


    هدف: استفاده از کتابخانه‌ی jsSHA

    می‌خواهیم در یک برنامه‌ی AngularJS 2.0، از کتابخانه‌ی jsSHA استفاده کرده و هش SHA512 یک رشته را محاسبه کنیم.


    تامین پیشنیازهای اولیه

    می‌توان فایل‌های این کتابخانه را مستقیما از GitHub دریافت و به پروژه اضافه کرد. اما بهتر است این‌کار را توسط npm مدیریت کنیم. به همین جهت فایل package.json آن‌را گشوده و سپس مدخل متناظری را به آن اضافه کنید:
    "dependencies": {
        // ...
        "jssha": "^2.1.0",
        // ...
      },
    به این ترتیب با اجرای دستور npm install بر روی پوشه‌ی جاری و یا ذخیره‌ی فایل در ویژوال استودیو، کار دریافت خودکار این کتابخانه صورت گرفته و در مسیر node_modules\jssha\src ذخیره می‌شود.


    بارگذاری فایل‌های کتابخانه به صورت پویا

    یک روش استفاده از این کتابخانه یا هر کتابخانه‌ی جاوا اسکریپتی، افزودن مدخل تعریف آن به صفحه‌ی index.html است:
     <script src="node_modules/jssha/src/sha512.js"></script>
    این روش هر چند کار می‌کند، اما با توجه به اینکه AngularJS 2.0 از System.JS برای مدیریت ماژول‌های خود کمک می‌گیرد، می‌تواند با روش پویای ذیل جایگزین شود. برای این منظور ابتدا فایل systemjs.config.js را باز کنید و سپس دو تغییر ذیل را به آن اعمال نمائید:
    // map tells the System loader where to look for things
    var map = {
        // ...
        'jssha': 'node_modules/jssha/src'
    };
     
    // packages tells the System loader how to load when no filename and/or no extension
    var packages = {
        // ...
        'jssha': { main: 'sha512.js', defaultExtension: 'js' }
    };
    در اینجا به اشیاء map و packages آن فایل که کار بارگذاری ماژول‌ها را به صورت خودکار انجام می‌دهد، تعاریف جدید jssha را اضافه کرده‌ایم. در قسمت map، مسیر پوشه‌ی فایل‌های js این کتابخانه مشخص شده‌اند و در قسمت packages، نام فایل اصلی مدنظر و پسوندهای آن‌ها ذکر گردید‌ه‌اند.
    به این ترتیب هر زمانیکه کار import این کتابخانه صورت گیرد، بارگذاری پویای آن انجام خواهد شد. به علاوه ابزارهای بسته بندی و deploy پروژه هم این فایل را پردازش کرده و به صورت خودکار، کار bundling، فشرده سازی و یکی سازی اسکریپت‌ها را انجام می‌دهند.


    استفاده از jsSHA به صورت untyped

    پس از دریافت بسته‌های این کتابخانه و مشخص سازی نحوه‌ی بارگذاری پویای آن، اکنون نوبت به استفاده‌ی از آن است. در اینجا منظور از untyped این است که فرض کنیم برای این کتابخانه، فایل‌های typings مخصوص TypeScript وجود ندارند و پس از جستجوی در مخزن کد https://github.com/DefinitelyTyped/DefinitelyTyped نتوانسته‌ایم معادلی را برای آن پیدا کنیم. بنابراین فایل جدید untyped-sha.component.ts را با محتوای ذیل به پروژه اضافه کنید:
    import { Component, OnInit } from '@angular/core';
     
    var jsSHA = require("jssha"); // ==> loads `sha512.js` file dynamically using `systemjs.config.js` file definitions
    //declare var jsSHA: any; // ==> this requires adding <script src="node_modules/jssha/src/sha512.js"></script> to the first page manually.
     
    @Component({
        templateUrl: 'app/using-third-party-libraries/untyped-sha.component.html'
    })
    export class UnTypedShaComponent implements OnInit {
        hash: String;
     
        ngOnInit(): void {
            let shaObj = new jsSHA("SHA-512", "TEXT");
            shaObj.update("This is a test");
            this.hash = shaObj.getHash("HEX");
        }
    }
    با این قالب untyped-sha.component.html
    <h1>SHA-512 Hash / UnTyped</h1>
     
    <p>String: This is a test</p>
    <p>HEX: {{hash}}</p>
    توضیحات
    هر زمانیکه فایل‌های typing یک کتابخانه‌ی جاوا اسکریپتی در دسترس نبودند، فقط کافی است از روش declare var jsSHA: any استفاده کنید. در اینجا any به همان حالت استاندارد و بی‌نوع جاوا اسکریپت اشاره می‌کند. در این حالت برنامه بدون مشکل کامپایل خواهد شد؛ اما از تمام مزایای TypeScript مانند بررسی نوع‌ها و همچنین intellisense محروم می‌شویم.
    در این مثال در hook ویژه‌ای به نام OnInit، کار ساخت شیء SHA را انجام داده و سپس هش عبارت This is a test محاسبه شده و به خاصیت عمومی hash انتساب داده می‌شود. سپس این خاصیت عمومی، در قالب این کامپوننت از طریق روش interpolation نمایش داده شده‌است.

    دو نکته‌ی مهم
    الف) اگر از روش declare var jsSHA: any استفاده کردید، کار بارگذاری فایل sha512.js به صورت خودکار رخ نخواهد داد؛ چون ماژولی را import نمی‌کند. بنابراین تعاریف systemjs.config.js ندید گرفته خواهد شد. در این حالت باید از همان روش متداول افزودن تگ script این کتابخانه به فایل index.html استفاده کرد.
    ب) برای بارگذاری پویای کتابخانه‌ی jsSHA بر اساس تعاریف فایل systemjs.config.js از متد require کمک بگیرید:
     var jsSHA = require("jssha");
    در این حالت باز هم متغیر jsSHA تعریف شده از نوع any است؛ اما اینبار متد require کار بارگذاری خودکار ماژولی را به نام jssha، انجام می‌دهد. این بارگذاری هم بر اساس تعاریف قسمت «بارگذاری فایل‌های کتابخانه به صورت پویا» ابتدای بحث کار می‌کند.


    استفاده از jsSHA به صورت typed

    کتابخانه‌ی jsSHA در مخزن کد https://github.com/DefinitelyTyped/DefinitelyTyped/tree/master/jssha دارای فایل d.ts. مخصوص خود است. برای نصب آن از یکی از دو روش ذیل استفاده کنید:
    الف) نصب دستی فایل‌های typings
     npm install -g typings
    typings install jssha --save --ambient
    توسط خط فرمان، به پوشه‌ی ریشه‌ی پروژه وارد شده و دو دستور فوق را صادر کنید. به این ترتیب فایل‌های d.ts. لازم، به پوشه‌ی typings پروژه اضافه می‌شوند.
    ب) تکمیل فایل typings.ts
    {
        "ambientDependencies": {
             // ...
            "jssha": "registry:dt/jssha#2.1.0+20160317120654"
        }
    }
    برای این منظور فایل typings.json را گشوده و سپس سطر جدید فوق را به آن اضافه کنید. اکنون اگر فایل package.json را یکبار دیگر ذخیره کنید و یا دستور npm install را صادر کنید، همان مراحل قسمت الف تکرار خواهند شد.

    پس از نصب فایل‌های typings این پروژه، به فایل main.ts مراجعه کرده و مدخل ذیل را به ابتدای آن اضافه کنید:
    /// <reference path="../typings/browser/ambient/jssha/index.d.ts" />
    اینکار سبب خواهد شد تا intellisense درون ویژوال استودیو بتواند مداخل متناظر را یافته و راهنمای مناسبی را ارائه دهد.

    در ادامه فایل جدید typed-sha.component.ts را با محتوای ذیل به پروژه اضافه کنید:
    import { Component, OnInit } from '@angular/core';
    //import { jsSHA } from "jssha";
    import * as jsSHA from "jssha"; // ===> var jsSHA = require("jssha"); // ===> loads `sha512.js` file dynamically using `systemjs.config.js` file definitions
     
    @Component({
        templateUrl: 'app/using-third-party-libraries/typed-sha.component.html'
    })
    export class TypedShaComponent implements OnInit{
        hash: String;
     
        ngOnInit(): void {
            let shaObj = new jsSHA("SHA-512", "TEXT");
            shaObj.update("This is a test");
            this.hash = shaObj.getHash("HEX");
        }
    }
    محتویات فایل typed-sha.component.html با محتویات فایل untyped-sha.component.html که پیشتر عنوان شد، یکی است.
    در اینجا تنها نکته‌ی مهم و جدید نسبت به روش قبل (استفاده از jsSHA به صورت untyped)، روش import این کتابخانه است. روش "import * as jsSHA from "jssha به عبارت var jsSHA = require("jssha") ترجمه می‌شود که در نهایت سبب بارگذاری خودکار فایل‌های jssha بر اساس تعاریف مداخل آن در فایل systemjs.config.js می‌گردد.


    کدهای کامل این پروژه را از اینجا می‌توانید دریافت کنید.
    مطالب
    Angular Material 6x - قسمت ششم - کار با فرم‌ها و دیالوگ‌ها
    در این قسمت قصد داریم به لیست فعلی کاربران و تماس‌های تعریف شده، تماس‌های جدیدی را اضافه کنیم و می‌خواهیم این‌کار را توسط دیالوگ‌های Popup بسته‌ی Angular Material انجام دهیم.


    معرفی سرویس MatDialog

    توسط سرویس MatDialog می‌توان modal dialogs بسته‌ی Angular Material را نمایش داد که به همراه طراحی متریال و پویانمایی مخصوص آن است.
     let dialogRef = dialog.open(UserProfileComponent,  { height: '400px’,  width: '600px’  });
    در اینجا یک صفحه‌ی دیالوگ، توسط متد open آن باز خواهد شد. پارامتر اول آن کامپوننتی است که باید بارگذاری شود و پارامتر دوم آن یک شیء تنظیمات اختیاری است. خروجی این متد وهله‌ای است از MatDialogRef و توسط آن می‌توان به دیالوگ باز شده دسترسی یافت:
    dialogRef.afterClosed().subscribe(result => {
       console.log(`Dialog result: ${result}`);
    });
    dialogRef.close('value');
    از آن می‌توان برای بستن dialog و یا دریافت پیامی پس از بسته شدن دیالوگ، استفاده کرد.
    در این مثال اگر dialogRef را با متد close و پارامتر value فراخوانی کنیم، سبب بسته شدن این دیالوگ خواهیم شد. این پارامتر در قسمت Dialog result پیام دریافتی پس از بسته شدن دیالوگ نیز قابل دسترسی است.
    کامپوننت‌هایی که توسط سرویس MatDialog نمایش داده می‌شوند، می‌توانند توسط سرویس جنریک MatDialogRef، صفحه‌ی دیالوگ باز شده را ببندند:
    @Component({/* ... */})
    export class YourDialog {
    
       constructor(public dialogRef: MatDialogRef<YourDialog>) { }
    
       closeDialog() {
          this.dialogRef.close('Value….!’);
       }
    }
    نکته‌ی مهم: چون سرویس MatDialog کار وهله سازی و نمایش کامپوننت‌ها را به صورت پویا انجام می‌دهد، محل تعریف این نوع کامپوننت‌های ویژه در قسمت entryComponents ماژول مرتبط است. به این ترتیب به A head of time compiler اعلام می‌کنیم که component factory این کامپوننت پویا است و باید به نحو ویژه‌ای مدیریت شود.

    نحوه‌ی طراحی یک دیالوگ نیز به کمک تعدادی کامپوننت و دایرکتیو میسر است:
    <h2 mat-dialog-title>Delete all</h2>
    <mat-dialog-content>Are you sure?</mat-dialog-content>
    <mat-dialog-actions>
        <button mat-button mat-dialog-close>No</button>
        <!-- Can optionally provide a result for the closing dialog. -->
        <button mat-button [mat-dialog-close]="true">Yes</button>
    </mat-dialog-actions>
    دایرکتیو mat-dialog-title سبب نمایش عنوان دیالوگ می‌شود. mat-dialog-content محتوای قابل اسکرول این دیالوگ را در بر می‌گیرد. mat-dialog-actions محلی است برای قرارگیری action buttons در پایین صفحه‌ی دیالوگ. در اینجا اگر ویژگی mat-dialog-close به true تنظیم شود، آن دکمه قابلیت بستن دیالوگ را پیدا می‌کند.


    ایجاد دکمه‌ی نمایش دیالوگ افزودن تماس‌ها و کاربران جدید

    قبل از هر کاری نیاز است دکمه‌ی افزودن یک کاربر جدید را به صفحه اضافه کنیم. برای اینکار یک منوی ویژه را در سمت راست، بالای صفحه ایجاد می‌کنیم. بنابراین ابتدا به مستندات toolbar و menu مراجعه می‌کنیم تا با نحوه‌ی تعریف دکمه‌ها و منوها به toolbar آشنا شویم. سپس فایل قالب toolbar\toolbar.component.html را به صورت زیر تکمیل می‌کنیم:
    <mat-toolbar color="primary">
      <button mat-button fxHide fxHide.xs="false" (click)="toggleSidenav.emit()">
        <mat-icon>menu</mat-icon>
      </button>
    
      <span>Contact Manager</span>
    
      <span fxFlex="1 1 auto"></span>
      <button mat-button [matMenuTriggerFor]="menu">
        <mat-icon>more_vert</mat-icon>
      </button>
      <mat-menu #menu="matMenu">
        <button mat-menu-item>New Contact</button>
      </mat-menu>
    </mat-toolbar>
    توسط یک span و سپس fxFlex تعریف شده‌ی آن سبب خواهیم شد تا mat-button بعدی و محتوای پس از آن به گوشه‌ی سمت راست toolbar هدایت شوند؛ در غیراینصورت دقیقا در کنار عبارت Contact manager ظاهر خواهند شد.
    سپس ابتدا یک mat-button را با آیکن more_vert (آیکن علامت بیشتر عمودی) تعریف کرده‌ایم:


    این دکمه توسط ویژگی matMenuTriggerFor به template reference variable ایی به نام menu متصل شده‌است تا با کلیک بر روی آن، این mat-menu را نمایش دهد:



    ایجاد دیالوگ افزودن تماس‌ها و کاربران جدید

    پس از تعریف دکمه و منویی که سبب نمایش عبارت افزودن یک تماس جدید می‌شوند، به رخ‌داد کلیک آن متدی را جهت نمایش صفحه‌ی دیالوگ جدید اضافه می‌کنیم:
     <button mat-menu-item (click)="openAddContactDialog()">New Contact</button>
    سپس در کدهای کامپوننت toolbar، کار مدیریت آن‌را انجام خواهیم داد. اما پیش از آن بهتر است کامپوننت جدیدی را که قرار است نمایش دهد به برنامه اضافه کنیم:
     ng g c contact-manager/components/new-contact-dialog --no-spec
    این دستور علاوه بر تولید کامپوننت جدید new-contact-dialog در پوشه‌ی components، کار تعریف مدخل آن‌را در ماژول این قسمت نیز انجام می‌دهد. اما همانطور که پیشتر نیز عنوان شد، باید آن‌را به لیست entryComponents اضافه کنیم تا بتوان آن‌را به صورت پویا بارگذاری کرد (در غیر اینصورت در زمان نمایش پویای آن، خطای no component factory for را دریافت می‌کنیم). بنابراین فایل contact-manager.module.ts را گشوده و به صورت زیر تکمیل می‌کنیم:
    import { NewContactDialogComponent } from "./components/new-contact-dialog/new-contact-dialog.component";
    
    @NgModule({
      declarations: [
        NewContactDialogComponent],
      entryComponents: [
        NewContactDialogComponent
      ]
    })
    export class ContactManagerModule { }
    اکنون می‌توانیم سرویس MatDialog را به سازنده‌ی کامپوننت toolbar تزریق کرده و از آن برای نمایش این کامپوننت جدید استفاده کنیم:
    import { Component, EventEmitter, OnInit, Output } from "@angular/core";
    import { MatDialog } from "@angular/material";
    
    import { NewContactDialogComponent } from "../new-contact-dialog/new-contact-dialog.component";
    
    @Component({
      selector: "app-toolbar",
      templateUrl: "./toolbar.component.html",
      styleUrls: ["./toolbar.component.css"]
    })
    export class ToolbarComponent implements OnInit {
    
      @Output() toggleSidenav = new EventEmitter<void>();
    
      constructor(private dialog: MatDialog) { }
    
      ngOnInit() { }
    
      openAddContactDialog(): void {
        const dialogRef = this.dialog.open(NewContactDialogComponent, { width: "450px" });
        dialogRef.afterClosed().subscribe(result => {
          console.log("The dialog was closed", result);
        });
      }
    }
    در اینجا سرویس MatDialog به سازنده‌ی کامپوننت تزریق شده و سپس از آن در متد openAddContactDialog که متصل به منوی نمایش «new contact» است، جهت نمایش پویای کامپوننت NewContactDialogComponent، استفاده می‌شود. اکنون اگر برنامه را اجرا کنیم و گزینه‌ی «new contact» را از منو انتخاب کنیم، چنین تصویری حاصل خواهد شد:



    تکمیل قالب کامپوننت تماس جدید

    در ادامه می‌خواهیم فرم افزودن یک تماس جدید را به همراه فیلدهای ورودی آن، به قالب new-contact-dialog.component.html اضافه کنیم:
    <h2 mat-dialog-title>Add new contact</h2>
    <mat-dialog-content>
      <div fxLayout="column">
    
      </div>
    </mat-dialog-content>
    <mat-dialog-actions>
      <button mat-button color="primary" (click)="save()">
        <mat-icon>save</mat-icon> Save
      </button>
      <button mat-button color="primary" (click)="dismiss()">
        <mat-icon>cancel</mat-icon> Cancel
      </button>
    </mat-dialog-actions>
    تا اینجا ساختار مقدماتی صفحه دیالوگ را مطابق توضیحاتی که در ابتدای بحث عنوان شد، تکمیل کردیم. یک عنوان توسط mat-dialog-title به آن اضافه شده‌است. سپس mat-dialog-content اضافه شده که در ادامه آن‌را تکمیل می‌کنیم. در آخر هم دکمه‌های action به این دیالوگ استاندارد اضافه شده‌اند. برای تکمیل متدهای save و dismiss این دکمه‌ها، کدهای ذیل را به کامپوننت new-contact-dialog.component.ts اضافه می‌کنیم:
    import { Component, OnInit } from "@angular/core";
    import { MatDialogRef } from "@angular/material";
    
    @Component()
    export class NewContactDialogComponent implements OnInit {
    
      constructor(
        private dialogRef: MatDialogRef<NewContactDialogComponent>
      ) { }
    
      ngOnInit() {
      }
    
      save() {
      }
    
      dismiss() {
        this.dialogRef.close(null);
      }
    }
    در اینجا توسط سرویس MatDialogRef از نوع NewContactDialogComponent، می‌توانیم به ارجاعی از سرویس MatDialog باز کننده‌ی آن دسترسی پیدا کنیم و برای نمونه در متد dismiss سبب بسته شدن این دیالوگ شویم.
    تا اینجا اگر برنامه را اجرا کنیم، به چنین شکلی خواهیم رسید:



    تکمیل فیلدهای ورود اطلاعات فرم ثبت یک تماس جدید

    تا اینجا ساختار فرم دیالوگ ثبت اطلاعات جدید را تکمیل کردیم. این فرم، به شیء user متصل خواهد شد. همچنین لیستی از avatars را هم جهت انتخاب، نمایش می‌دهد. به همین جهت این دو خاصیت عمومی را به کدهای کامپوننت آن اضافه می‌کنیم:
    import { User } from "../../models/user";
    
    @Component()
    export class NewContactDialogComponent implements OnInit {
    
      avatars = ["user1", "user2", "user3", "user4", "user5", "user6", "user7", "user8"];
      user: User = { id: 0, birthDate: new Date(), name: "", avatar: "", bio: "", userNotes: null };
    در ادامه فیلدهای آن‌را به صورت زیر در قسمت mat-dialog-content اضافه خواهیم کرد:


    الف) فیلد نمایش و انتخاب avatar کاربر
        <mat-form-field>
          <mat-select placeholder="Avatar" [(ngModel)]="user.avatar">
            <mat-select-trigger>
              <mat-icon svgIcon="{{user.avatar}}"></mat-icon> {{ user.avatar }}
            </mat-select-trigger>
            <mat-option *ngFor="let avatar of avatars" [value]="avatar">
              <mat-icon svgIcon="{{avatar}}"></mat-icon> {{ avatar }}
            </mat-option>
          </mat-select>
        </mat-form-field>


    در اینجا از کامپوننت mat-select برای انتخاب avatar کاربر استفاده شده‌است که نتیجه‌ی نهایی انتخاب آن به خاصیت user.avatar متصل شده‌است.
    گزینه‌های این لیست (mat-option) بر اساس آرایه‌ی avatars که در کامپوننت تعریف کردیم، تامین می‌شوند که در اینجا از mat-icon برای نمایش آیکن مرتبط نیز استفاده شده‌است. در این مورد در قسمت قبل چهارم، بخش «بارگذاری و معرفی فایل svg نمایش avatars کاربران به Angular Material» بیشتر توضیح داده شده‌است.
    کار mat-select-trigger، سفارشی سازی برچسب نمایشی این کنترل است.

    ب) فیلد دریافت نام کاربر به همراه اعتبارسنجی آن
        <mat-form-field>
          <input matInput placeholder="Name" #name="ngModel" [(ngModel)]="user.name" required>
          <mat-error *ngIf="name.invalid && name.touched">You must enter a name</mat-error>
        </mat-form-field>


    در اینجا فیلد نام کاربر، به user.name متصل و همچنین توسط ویژگی required، پر کردن آن الزامی اعلام شده‌است. به همین جهت در تصویر فوق یک ستاره را نیز کنار آن مشاهده می‌کند که به صورت خودکار توسط Angular Material نمایش داده شده‌است.
    از کامپوننت mat-error برای نمایش خطاهای اعتبارسنجی یک فیلد استفاده می‌شود که نمونه‌ای از آن‌را در اینجا با بررسی خواص invalid  و touched فیلد نام که بر اساس ویژگی required فعال می‌شوند، مشاهده می‌کنید. بدیهی است در اینجا به هر تعدادی که نیاز است می‌توان mat-error را قرار داد.

    ج) فیلد دریافت تاریخ تولد کاربر توسط یک date picker
        <mat-form-field>
          <input matInput [matDatepicker]="picker" placeholder="Born" [(ngModel)]="user.birthDate">
          <mat-datepicker-toggle matSuffix [for]="picker"></mat-datepicker-toggle>
          <mat-datepicker #picker></mat-datepicker>
        </mat-form-field>


    در اینجا از کامپوننت mat-datepicker برای انتخاب تاریخ تولید یک شخص استفاده شده‌است و نتیجه‌ی آن به خاصیت user.birthDate متصل خواهد شد.
    برای افزودن آن ابتدا یک mat-datepicker را به mat-form-field اضافه می‌کنیم. سپس یک template reference variable را به آن نسبت خواهیم داد. از آن هم در فیلد ورودی با انتساب آن به ویژگی matDatepicker و هم در کامپوننت mat-datepicker-toggle که سبب نمایش آیکن انتخاب تقویم می‌شود، در ویژگی for آن استفاده خواهیم کرد.

    د) فیلد چند سطری دریافت توضیحات و شرح‌حال کاربر
        <mat-form-field>
          <textarea matInput placeholder="Bio" [(ngModel)]="user.bio"></textarea>
        </mat-form-field>


    در اینجا برای دریافت توضیحات چندسطری، از یک text area استفاده شده‌است که به خاصیت user.bio متصل است.

    بنابراین همانطور که ملاحظه می‌کنید، روش طراحی فرم‌های Angular Material ویژگی‌های خاص خودش را دارد:
    - دایرکتیو matInput را می‌توان به المان‌های استاندارد input و textarea اضافه کرد تا داخل mat-form-field نمایش داده شوند. این mat-form-field است که کار اعمال CSS ویژه‌ی طراحی متریال را انجام می‌دهد و امکان نمایش پیام‌های خطای اعتبارسنجی و پویانمایی ورود اطلاعات را سبب می‌شود.
    - قسمت mat-dialog-content را توسط fxLayout به حالت ستونی تنظیم کردیم:
    <mat-dialog-content>
      <div fxLayout="column">
    
      </div>
    </mat-dialog-content>
    این مورد است که سبب خواهد شد المان‌های فرم از بالا به پایین نمایش داده شوند. در غیراینصورت mat-form-fieldها دقیقا در کنار هم قرار می‌گیرند.
    برای مثال اگر خواستید المان‌های فرم با فاصله‌ی بیشتری از هم قرار بگیرند، می‌توان از fxLayoutGap استفاده کرد که در مورد آن در قسمت دوم «معرفی Angular Flex layout» بیشتر توضیح داده شد.


    تکمیل سرویس کاربران جهت ذخیره‌ی اطلاعات تماس کاربر جدید

    در ادامه می‌خواهیم با کلیک کاربر بر روی دکمه‌ی Save، ابتدا این اطلاعات به سمت سرور ارسال و سپس در سمت سرور ذخیره شوند. پس از آن، Id این کاربر جدید به سمت کلاینت بازگشت داده شود، دیالوگ جاری بسته و در آخر این شیء جدید به لیست تماس‌های نمایش داده‌ی شده‌ی در sidenav اضافه گردد.

    الف) تکمیل سرویس Web API سمت سرور
    در ابتدا متد Post را به Web API برنامه جهت ذخیره سازی اطلاعات User ارسالی از سمت کلاینت اضافه می‌کنیم. کدهای کامل آن‌را از فایل پیوستی انتهای بحث می‌توانید دریافت کنید:
    namespace MaterialAspNetCoreBackend.WebApp.Controllers
    {
        [Route("api/[controller]")]
        public class UsersController : Controller
        {
            private readonly IUsersService _usersService;
    
            public UsersController(IUsersService usersService)
            {
                _usersService = usersService ?? throw new ArgumentNullException(nameof(usersService));
            }
    
            [HttpPost]
            public async Task<IActionResult> Post([FromBody] User user)
            {
                if (!ModelState.IsValid)
                {
                    return BadRequest(ModelState);
                }
                await _usersService.AddUserAsync(user);
                return Created("", user);
            }
        }
    }

    ب) تکمیل سرویس کاربران سمت کلاینت
    سپس به فایل user.service.ts مراجعه کرده و دو تغییر زیر را به آن اضافه می‌کنیم:
    @Injectable({
      providedIn: "root"
    })
    export class UserService {
    
      private usersSource = new BehaviorSubject<User>(null);
      usersSourceChanges$ = this.usersSource.asObservable();
    
      constructor(private http: HttpClient) { }
    
      addUser(user: User): Observable<User> {
        const headers = new HttpHeaders({ "Content-Type": "application/json" });
        return this.http
          .post<User>("/api/users", user, { headers: headers }).pipe(
            map(response => {
              const addedUser = response || {} as User;
              this.notifyUsersSourceHasChanged(addedUser);
              return addedUser;
            }),
            catchError((error: HttpErrorResponse) => throwError(error))
          );
      }
    
      notifyUsersSourceHasChanged(user: User) {
        this.usersSource.next(user);
      }
    }
    کار متد addUser، ارسال اطلاعات فرم ثبت یک تماس جدید به سمت سرور و Web API برنامه است. پس از ثبت موفقیت آمیز کاربر در سمت سرور، متد return Created آن:
     return Created("", user);
    سبب خواهد شد تا بتوانیم در سمت کلاینت، به Id اطلاعات رکورد جدید دسترسی داشته باشیم. مزیت آن امکان افزودن این رکورد به لیست کاربران sidenav و همچنین فعالسازی مسیریابی آن است که بر اساس این Id واقعی کار می‌کند.
    بنابراین نیاز است از طریق این سرویس به کامپوننت sidenav، در مورد تغییرات لیست کاربران اطلاعات رسانی کنیم که روش کار آن‌را پیشتر در مطلب «صدور رخدادها از سرویس‌ها به کامپوننت‌ها در برنامه‌های Angular» نیز مرور کرده‌ایم. برای این منظور یک BehaviorSubject از نوع User را تعریف کرده‌ایم که اشتراک به آن از طریق خاصیت عمومی usersSourceChanges میسر است. هر زمانیکه متد next آن فراخوانی شود، تمام مشترکین به آن، از افزوده شدن کاربر جدید، به همراه اطلاعات کامل آن مطلع خواهند شد.

    ج) تکمیل متد save کامپوننت new-contact-dialog
    پس از تکمیل سرویس کاربران جهت افزودن متد addUser به آن، اکنون می‌توانیم از آن در کامپوننت دیالوگ افزودن اطلاعات تماس جدید استفاده کنیم:
    import { UserService } from "../../services/user.service";
    
    @Component()
    export class NewContactDialogComponent {
    
      user: User = { id: 0, birthDate: new Date(), name: "", avatar: "", bio: "", userNotes: null };
    
      constructor(
        private dialogRef: MatDialogRef<NewContactDialogComponent>,
        private userService: UserService
      ) { }
    
      save() {
        this.userService.addUser(this.user).subscribe(data => {
          console.log("Saved user", data);
          this.dialogRef.close(data);
        });
      }
    }
    در اینجا در متد save، ابتدا متد addUser سرویس افزودن اطلاعات جدید فراخوانی می‌شود. سپس در صورت موفقیت آمیز بودن عملیات، توسط سرویس dialogRef، این صفحه‌ی دیالوگ نیز به صورت خودکار بسته خواهد شد. همچنین به متد close آن data دریافتی از سرور ارسال شده‌است. این data در toolbar.component در قسمت dialogRef.afterClosed قابل دسترسی خواهد بود.

    د) تکمیل کامپوننت sidenav جهت واکنش نشان دادن به افزوده شدن اطلاعات تماس جدید
    اکنون که سرویس کاربران به صفحه دیالوگ افزودن اطلاعات یک تماس جدید متصل شده‌است، نیاز است بتوانیم اطلاعات کاربر جدید را به لیست تماس‌های sidenav اضافه کنیم. به همین جهت به sidenav.component مراجعه کرده و مشترک usersSourceChanges سرویس کاربران خواهیم شد:
    import { UserService } from "../../services/user.service";
    
    @Component()
    export class SidenavComponent implements OnInit, OnDestroy {
    
      users: User[] = [];
      subscription: Subscription | null = null;
    
      constructor(
        private userService: UserService) { }
    
      ngOnInit() {
        this.subscription = this.userService.usersSourceChanges$.subscribe(user => {
          if (user) {
            this.users.push(user);
          }
        });
      }
    
      ngOnDestroy() {
        if (this.subscription) {
          this.subscription.unsubscribe();
        }
      }
    }
    ابتدا در ngOnInit توسط سرویس کاربران، مشترک تغییرات usersSourceChanges خواهیم شد. در اینجا اگر کاربر جدیدی به لیست اضافه شده باشد، آن‌را توسط متد push به لیست کاربران جاری sidenav اضافه می‌کنیم تا بلافاصله در لیست نمایش داده شود.


    استفاده از کامپوننت Snackbar جهت نمایش موفقیت آمیز بودن ثبت اطلاعات

    متد save کامپوننت دیالوگ یک تماس جدید را به صورت زیر تکمیل کردیم:
      save() {
        this.userService.addUser(this.user).subscribe(data => {
          console.log("Saved user", data);
          this.dialogRef.close(data);
        });
    در اینجا data ارسال شده‌ی به متد close در کامپوننت toolbar در قسمت dialogRef.afterClosed قابل دسترسی خواهد بود:
      openAddContactDialog(): void {
        const dialogRef = this.dialog.open(NewContactDialogComponent, { width: "450px" });
        dialogRef.afterClosed().subscribe(result => {
          console.log("The dialog was closed", result);
        });
      }
    بنابراین در ادامه قصد داریم از آن جهت نمایش یک snackbar به همراه ارائه لینک هدایت به صفحه‌ی جزئیات تماس جدید، استفاده کنیم:


    کدهای کامل این تغییرات را در ذیل مشاهده می‌کنید:
    @Component()
    export class ToolbarComponent {
    
      @Output() toggleSidenav = new EventEmitter<void>();
    
      constructor(private dialog: MatDialog, private snackBar: MatSnackBar,  private router: Router) { }
    
      openAddContactDialog(): void {
        const dialogRef = this.dialog.open(NewContactDialogComponent, { width: "450px" });
        dialogRef.afterClosed().subscribe((result: User) => {
          console.log("The dialog was closed", result);
          if (result) {
            this.openSnackBar(`${result.name} contact has been added.`, "Navigate").onAction().subscribe(() => {
              this.router.navigate(["/contactmanager", result.id]);
            });
          }
        });
      }
    
      openSnackBar(message: string, action: string): MatSnackBarRef<SimpleSnackBar> {
        return this.snackBar.open(message, action, {
          duration: 5000,
        });
      }
    }
    توضیحات:
    برای گشودن snackbar که نمونه‌ای از آن‌را در تصویر فوق ملاحظه می‌کنید، ابتدا نیاز است سرویس MatSnackBar را به سازنده‌ی کلاس تزریق کرد. سپس توسط آن می‌توان یک کامپوننت مستقل را همانند دیالوگ‌ها نمایش داد و یا می‌توان یک متن را به همراه یک Action منتسب به آن، به کاربر نمایش داد؛ مانند متد openSnackBar که در کامپوننت فوق از آن استفاده می‌شود. این متد در رخ‌داد پس از بسته شدن dialog، نمایش داده شده‌است.
    پارامتر اول آن پیامی است که توسط snackbar نمایش داده می‌شود و پارامتر دوم آن، برچسب دکمه مانندی است کنار این پیام، که سبب انجام عملی خواهد شد و در اینجا به آن Action گفته می‌شود. برای مدیریت آن باید متد onAction را فراخوانی کرد و مشترک آن شد. در این حالت اگر کاربر بر روی این دکمه‌ی action کلیک کند، سبب هدایت خودکار او به صفحه‌ی نمایش جزئیات اطلاعات تماس کاربر خواهیم شد. به همین جهت سرویس Router نیز به سازنده‌ی کلاس تزریق شده‌است تا بتوان از متد navigate آن استفاده کرد.



    کدهای کامل این قسمت را از اینجا می‌توانید دریافت کنید: MaterialAngularClient-05.zip
    برای اجرای آن:
    الف) ابتدا به پوشه‌ی src\MaterialAngularClient وارد شده و فایل‌های restore.bat و ng-build-dev.bat را اجرا کنید.
    ب) سپس به پوشه‌ی src\MaterialAspNetCoreBackend\MaterialAspNetCoreBackend.WebApp وارد شده و فایل‌های restore.bat و dotnet_run.bat را اجرا کنید.
    اکنون برنامه در آدرس https://localhost:5001 قابل دسترسی است.