Blazor 5x - قسمت هفتم - مبانی Blazor - بخش 4 - انتقال اطلاعات از کامپوننت‌های فرزند به کامپوننت والد
اندازه‌ی قلم متن
تخمین مدت زمان مطالعه‌ی مطلب: پنج دقیقه

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


در این تصویر، checkboxهای انتخاب شده، درون کامپوننت‌های مجزای فرزند و گزارش جمع نهایی ارائه شده، در کامپوننت والد قرار دارند.


معرفی Event Call Back

در این قسمت قصد داریم به کامپوننت Pages\LearnBlazor\LearnBlazor‍Components\IndividualRoom.razor که در مثال این سری تکمیل کردیم، یک checkbox انتخاب را نیز اضافه کنیم تا کاربرها بتوانند در زمان نمایش لیست اتاق‌ها در کامپوننت Pages\LearnBlazor\DemoHotel.razor، بیش از یک اتاق را انتخاب کنند.
برای این منظور در ابتدا به کامپوننت DemoHotel مراجعه کرده و فیلد SelectedRooms را در آن تعریف کرده و ذیل عنوان Hotel Rooms نمایش می‌دهیم:
@page "/demoHotel"

        <div class="col-12">
            <h4 class="text-info">Hotel Rooms</h4>
            <span>Rooms Selected - @SelectedRooms</span>
        </div>
        @foreach (var room in Rooms)
        {
            <IndividualRoom Room="room"></IndividualRoom>
        }

@code{

    int SelectedRooms;

    void RoomSelectionCounterChanged(bool isRoomSelected)
    {
        if (isRoomSelected)
        {
            SelectedRooms++;
        }
        else
        {
            SelectedRooms--;
        }
    }
    // ...
}
همچنین متد RoomSelectionCounterChanged را هم برای تغییر مقدار SelectedRooms تعریف کرده‌ایم. اما این متد به تنهایی کار نمی‌کند و باید بتوان آن‌را به کامپوننت فرزند <IndividualRoom Room="room"></IndividualRoom> انتقال داد تا در زمان انتخاب آن اتاق (و یا عدم انتخاب آن) در کامپوننت IndividualRoom، پارامتر bool isRoomSelected بر اساس وضعیت checkbox آن دریافت شده و در نتیجه مقدار جاری SelectedRooms، یک واحد کم یا زیاد شود. بنابراین نیاز است تا بتوان اشاره‌گری از یک متد کامپوننت سطح بالا را به یک کامپوننت سطح پایین و فرزند آن انتقال داد. اینکار در Blazor توسط EventCallback‌ها انجام می‌شود.
در ادامه به کامپوننت IndividualRoom.razor مراجعه کرده و کدهای آن را به صورت زیر تغییر می‌دهیم:
<input type="checkbox" @onchange="RoomCheckBoxSelectionChanged" />

@code
{
    //...

    [Parameter]
    public EventCallback<bool> OnRoomCheckBoxSelection { get; set; }

    async Task RoomCheckBoxSelectionChanged(ChangeEventArgs e)
    {
        await OnRoomCheckBoxSelection.InvokeAsync((bool)e.Value);
    }
}
ابتدا یک checkbox را جهت فراهم آوردن امکان انتخاب یک اتاق اضافه کرده‌ایم. سپس رخ‌داد onchange آن‌را به متد RoomCheckBoxSelectionChanged متصل کرده‌ایم. نوع پارامتر این متد را با نزدیک کردن اشاره‌گر ماوس به onchange@ چک باکس می‌توان مشاهده کرد:


تا اینجا فقط یک رخ‌داد را به یک متد، در همان کامپوننت متصل کرده‌ایم. هربار که checkbox تعریف شده انتخاب شود، متد رویدادگردان RoomCheckBoxSelectionChanged اجرا می‌شود. مرحله‌ی بعد، انتقال اطلاعات آن، به کامپوننت والد است که اینکار توسط پارامتر OnRoomCheckBoxSelection صورت می‌گیرد. کار آن، انتقال وضعیت checkbox، به متد RoomSelectionCounterChanged کامپوننت والد است.
بنابراین در اینجا نیاز است تا بتوان ارجاعی از این متد کامپوننت والد را به کامپوننت فرزند ارسال کرد که EventCallback تعریف شده‌ی به صورت پارامتر، چنین هدفی را برآورده می‌کند. با پارامتر تعریف شدن آن، می‌توان OnRoomCheckBoxSelection را به صورت زیر، به هر المان تعریف کننده‌ی کامپوننت IndividualRoom در کامپوننت DemoHotel اضافه کرد:
@foreach (var room in Rooms)
{
    <IndividualRoom OnRoomCheckBoxSelection="RoomSelectionCounterChanged" Room="room"></IndividualRoom>
}
در این تعریف، پارامتر Room، یک پارامتر ورودی است و پارامتر OnRoomCheckBoxSelection، به نوعی یک پارامتر خروجی است که با اتصال به متدی در همین کامپوننت، امکان دریافت اطلاعات و رویدادها را از یک کامپوننت سطح پایین‌تر پیدا می‌کند.

بنابراین به صورت خلاصه، هر زمانیکه یک checkbox در کامپوننت IndividualRoom انتخاب می‌شود، در نتیجه‌ی آن متد منتسب به EventCallback ارسالی به آن کامپوننت نیز فراخوانی می‌گردد که اینکار، سبب اجرای کدی در کامپوننت والد خواهد شد.


یک تمرین: انتقال رویداد انتخاب شدن یک div به کامپوننت والد

در بخش 2، در انتهای مطلب، لیست امکانات رفاهی هتل را هم نمایش دادیم. در اینجا می‌خواهیم اگر کاربری بر روی خروجی کامپوننت یکی از امکانات موجود کلیک کرد (کلیک بر روی div آن)، نام آن ویژگی در کامپوننت والد نمایش داده شود.
برای این منظور کامپوننت Pages\LearnBlazor\LearnBlazor‍Components\IndividualAmenity.razor را به صورت زیر تغییر می‌دهیم:
<div class="bg-light border p-2 col-5 offset-1 mt-2"
    @onclick="args => AmenitySelectionChanged(args, Amenity.Name)">
    <h4 class="text-secondary">Amenity - @Amenity.Id</h4>
    @Amenity.Name<br />
    @Amenity.Description<br />
</div>

@code
{
    [Parameter]
    public BlazorAmenity Amenity { get; set; }

    [Parameter]
    public EventCallback<string> OnAmenitySelection { get; set; }

    protected async Task AmenitySelectionChanged(MouseEventArgs e, string name)
    {
        await OnAmenitySelection.InvokeAsync(name);
    }
}
هدف از این مثال، آشنایی با نحوه‌ی تغییر امضای متد منتسب به رویداد onclick@ است. چون نوع پارامتر متد متناظر با آن، از نوع MouseEventArgs است (<EventCallback<MouseEventArgs) و نه از نوع ChangeEventArgs مثال قبلی که به همراه خاصیت Value مفیدی بود:


در اینجا نیاز خواهیم داشت تا اطلاعات رشته‌ای نام Amenity جاری را پس از کلیک بر روی div، به کامپوننت والد انتقال دهیم و MouseEventArgs فقط به همراه اطلاعات مختصات محل قرارگیری اشاره‌گر ماوس است. در یک چنین حالتی می‌توان با استفاده از anonymous method زیر، امضای متد منتسب به آن‌را تغییر داد:
@onclick="args => AmenitySelectionChanged(args, Amenity.Name)"
اکنون با هر بار کلیک بر روی div، نام Amenity جاری از طریق EventCallback تعریف شده، به سمت کامپوننت والد ارسال می‌شود. بنابراین مرحله‌ی بعدی، مراجعه به کامپوننت DemoHotel.razor است و استفاده از پارامتر جدید OnAmenitySelection:
@page "/demoHotel"

    @foreach (var amenity in AmenitiesList)
    {
      <IndividualAmenity OnAmenitySelection="AmenitySelectionChanged" Amenity="amenity"></IndividualAmenity>
    }

    <div class="col-12">
        <p class="text-secondary"> Selected Amenity : @SelectedAmenity </p>
    </div>

@code{

    string SelectedAmenity = "";

    void AmenitySelectionChanged(string amenity)
    {
        SelectedAmenity = amenity;
    }
}
ابتدا پارامتر جدید OnAmenitySelection کامپوننت IndividualAmenity به متد AmenitySelectionChanged همین کامپوننت متصل می‌شود. امضای آن بر اساس نوع پارامتر <EventCallback<string کامپوننت IndividualAmenity تعیین شده‌است.
سپس این پارامتر رشته‌ای دریافتی، به فیلد جدید SelectedAmenity انتساب داده می‌شود که پس از پایان این متد و رویداد، سبب درج آن در زیر حلقه‌ی نمایش AmenitiesList خواهد شد:



کدهای کامل این مطلب را از اینجا می‌توانید دریافت کنید: Blazor-5x-Part-07.zip
  • #
    ‫۳ سال و ۶ ماه قبل، پنجشنبه ۱۴ اسفند ۱۳۹۹، ساعت ۲۳:۰۴
    با سلام
    در بخش << یک تمرین: انتقال رویداد انتخاب شدن یک div به کامپوننت والد >>
    درسته که امضای متد از نوع  MouseEventArgs هست اما لازم نیست حتماً امضای متد رو تقلید کنیم و یک نوع  MouseEventArgs به متد ارسال کنیم به جاش میشه از این شکل هم استفاده کرد
    <div class="bg-light border p-2 col-5 offset-1 mt-2"
        @onclick="@(() => AmenitySelectionChanged(Amenity.Name))">
        <h4 class="text-secondary">Amenity - @Amenity.Id</h4>
        @Amenity.Name<br />
        @Amenity.Description<br />
    </div>
    
    @code
    {
        [Parameter]
        public BlazorAmenity Amenity { get; set; }
    
        [Parameter]
        public EventCallback<string> OnAmenitySelection { get; set; }
    
        protected async Task AmenitySelectionChanged(string name)
        {
            await OnAmenitySelection.InvokeAsync(name);
        }
    }

    • #
      ‫۳ سال و ۶ ماه قبل، جمعه ۱۵ اسفند ۱۳۹۹، ساعت ۰۰:۴۲
      بله. روش دیگری هم در قسمت بعدی برای این مثال عنوان شده با تعداد پارامتر بیشتر و کمی خلاصه‌تر.
  • #
    ‫۳ سال قبل، شنبه ۶ شهریور ۱۴۰۰، ساعت ۱۸:۱۷
    یک نکته‌ی تکمیلی: نیاز به دقت در ویژگی «captured into the closure» در حلقه‌های Blazor

    برای مثال حلقه‌ی زیر را در نظر بگیرید:
    @for( int c = 0; c < 10; c++ )
    {
       <li>
           <a href="#" @onclick="@(_=> OnLinkClicked(c))">@c</a>
       </li>
    }
    فکر می‌کنید پس از پایان این حلقه و رندر UI، اگر بر روی لینکی کلیک شد، چه مقداری به متد OnLinkClicked ارسال می‌شود؟
    برخلاف تصور، با کلیک بر روی تمام لینک‌ها، فقط عدد ثابت 10 به متد  OnLinkClicked ارسال می‌شود. علت آن، همان نکات مطلب «بررسی مفهوم Captured Variable در زبان سی شارپ» است که در حین تشکیل حلقه‌های Blazor هم صادق هستند.
    برای رفع این مشکل، از یکی از دو روش زیر می‌توان استفاده کرد:
    Capture متغیر داخل حلقه:
    @for( int c = 0; c < 10; c++ )
    {
       var current = c;
       <li>
           <a href="#" @onclick="@(_=> OnLinkClicked(current))">@current</a>
       </li>
    }
    و یا ایجاد یک حلقه‌ی foreach بر روی یک Enumerable:
    @foreach(var c in Enumerable.Range(0,10))
    {
       <li>
           <a href="#" @onclick="@(_=> OnLinkClicked(c))">@c</a>
       </li>
    }