عنوان میشود که HTML over Web socket آیندهی توسعهی برنامههای وب است و این آینده هم اکنون توسط Blazor Server در دسترس است. در این مدل توسعه، ابتدا یک اتصال SignalR برقرار شده و سپس تمام تعاملات بین سرور و کلاینت، از طریق همین اتصال که عموما web socket است، مدیریت میشود. به همین جهت در ادامه قصد داریم یک پروژهی Blazor Server را تکمیل کنیم. پس از آن یک پروژهی Blazor WASM را نیز بررسی خواهیم کرد. بنابراین هر دو مدل توسعهی برنامههای Blazor را پوشش خواهیم داد. برای این منظور در ابتدا مبانی Blazor را بررسی میکنیم که در هر دو مدل یکی است.
تعریف مدل برنامه
در همان پروژهی خالی Blazor Server که در قسمت دوم با دستور dotnet new blazorserver ایجاد کردیم، پوشهی Models را افزوده و کلاس BlazorRoom را در آن تعریف میکنیم:
namespace BlazorServerSample.Models
{
public class BlazorRoom
{
public int Id { set; get; }
public string Name { set; get; }
public decimal Price { set; get; }
public bool IsActive { set; get; }
}
}
سپس برای اینکه مدام نیاز به تعریف فضای نام آن در فایلهای مختلف razor. برنامه نباشد، به فایل Imports.razor_ مراجعه کرده و سطر زیر را به انتهای آن اضافه میکنیم:
@using BlazorServerSample.Models
برنامه را نیز توسط دستور dotnet watch run اجرا میکنیم.
Data binding یک طرفه
در ادامه به فایل Pages\Index.razor مراجعه کرده و منهای سطر اول مسیریابی آن، مابقی محتوای آنرا حذف میکنیم. در اینجا میخواهیم مقادیر نمونهای از شیء BlazorRoom را نمایش دهیم. به همین جهت این شیء را در قسمت code@ فایل razor جاری (همانند نکات
قسمت قبل)، ایجاد میکنیم:
@page "/"
<h2 class="bg-light border p-2">
First Room
</h2>
Room: @Room.Name
<br/>
Price: @Room.Price
@code
{
BlazorRoom Room = new BlazorRoom
{
Id = 1,
Name = "Room 1",
IsActive = true,
Price = 499
};
}
در اینجا در ابتدا شیء Room را در قسمت قطعه کد فایل razor جاری ایجاد کرده و سپس اطلاعات آنرا با استفاده از زبان Razor نمایش دادهایم.
به این روش نمایش اطلاعات، one-way data-binding نیز گفته میشود. اما چطور میتوان یک طرفه بودن آنرا متوجه شد؟ برای این منظور یک text-box را نیز در ذیل تعاریف فوق، به صورت زیر اضافه میکنیم که مقدارش را از Room.Price دریافت میکند:
<input type="number" value="@Room.Price" />
اکنون اگر این مقدار را تغییر دهیم، عدد جدید قیمت اتاق، به خاصیت Room.Price منعکس نمیشود و تغییری نمیکند:
Data binding دو طرفه
اکنون میخواهیم اگر مقدار ورودی Room.Price توسط text-box فوق تغییر کرد، نتیجهی نهایی، به خاصیت متناظر با آن نیز اعمال شود و تغییر کند. برای این منظور فقط کافی است ویژگی value را به bind-value@ تغییر دهیم:
<input type="number" @bind-value="@Room.Price" />
ویژگی bind-value@ سبب برقراری data-binding دو طرفه میشود. یعنی در ابتدا مقدار اولیهی خاصیت Room.Price را نمایش میدهد. در ادامهی اگر کاربر، مقدار این text-box را تغییر داد، نتیجهی نهایی را به خاصیت Room.Price نیز اعمال میکند و همچنین این تغییر، سبب به روز رسانی UI نیز میشود؛ یعنی در جائیکه پیشتر مقدار اولیهی Room.Price را نمایش داده بودیم، اکنون مقدار جدید آن نمایش داده خواهد شد:
البته اگر برنامه را اجرا کنیم، با تغییر مقدار text-box، بلافاصله تغییری را مشاهده نخواهیم کرد. برای اعمال تغییرات نیاز خواهد بود تا در جائی خارج از text-box کلیک و focus را به المانی دیگر منتقل کنیم. اگر میخواهیم همراه با تایپ اطلاعات درون text-box، رابط کاربری نیز به روز شود، میتوان bind-value را به یک رخداد خاص، مانند oninput متصل کرد. حالت پیشفرض آن onchange است:
<input type="number" @bind-value="@Room.Price" @bind-value:event="oninput" />
اکنون اگر برنامه را اجرا کرده و درون text-box اطلاعاتی را وارد کنیم، بلافاصله UI نیز به روز رسانی خواهد شد.
لیست کامل رخدادها را
در اینجا میتوانید مشاهده کنید. برای مثال برای یک المان input، دو رخداد onchange و oninput قابل تعریف هستند.
یک نکته: در حین کار با bind-value@، نیازی نیست مقدار آن با @ شروع شود. یعنی ذکر "bind-value="Room.Price@ نیز کافی است.
تمرین 1 - خاصیت IsActive یک اتاق را به یک checkbox متصل کرده و همچنین وضعیت جاری آنرا نیز در یک برچسب نمایش دهید.
در اینجا میخواهیم مقدار خاصیت Room.IsActive را توسط یک اتصال دو طرفه، به یک checkbox متصل کنیم:
<input type="checkbox" @bind-value="Room.IsActive" />
<br/>
This room is @(Room.IsActive? "Active" : "Inactive").
با استفاده از bind-value@، وضعیت جاری خاصیت Room.IsActive را به یک checkbox متصل کردهایم. همچنین در ادامه توسط یک عبارت شرطی، این وضعیت را نمایش دادهایم.
بار اولی که برنامه نمایش داده میشود، هر چند مقدار IsActive بر اساس مقدار دهی آن در شیء Room، مساوی true است، اما chekbox، علامت نخورده باقی میماند. برای رفع این مشکل نیاز است ویژگی checked این المان را نیز به صورت زیر مقدار دهی کرد:
<input type="checkbox" @bind-value="Room.IsActive"
checked="@(Room.IsActive? "cheked" : null)" />
در این حالت اگر اتاقی فعال باشد، مقدار ویژگی checked، به checked و در غیراینصورت به null تنظیم میشود. به این ترتیب مشکل عدم نمایش checkbox انتخاب شده در بار اول نمایش کامپوننت جاری، برطرف میشود.
اتصال خواص مدلها به dropdownها
اکنون میخواهیم مدل این مثال را کمی توسعه داده و خواص تو در تویی را به آن اضافه کنیم:
using System.Collections.Generic;
namespace BlazorServerSample.Models
{
public class BlazorRoom
{
// ...
public List<BlazorRoomProp> RoomProps { set; get; }
}
public class BlazorRoomProp
{
public int Id { set; get; }
public string Name { set; get; }
public string Value { set; get; }
}
}
برای مثال یک اتاق میتواند ویژگیهایی مانند مساحت، تعداد نفرات مجاز و غیره را داشته باشد. هدف از ویژگی جدید RoomProps، تعیین لیست این نوع موارد است.
پس از این تعاریف، فیلد Room را به صورت زیر به روز رسانی میکنیم تا تعدادی از خواص اتاق را به همراه داشته باشد:
@code
{
BlazorRoom Room = new BlazorRoom
{
Id = 1,
Name = "Room 1",
IsActive = true,
Price = 499,
RoomProps = new List<BlazorRoomProp>
{
new BlazorRoomProp
{
Id = 1, Name = "Sq Ft", Value = "100"
},
new BlazorRoomProp
{
Id = 2, Name = "Occupancy", Value = "3"
}
}
};
}
در ادامه میخواهیم این خواص را در یک dropdown نمایش دهیم. همچنین با انتخاب یک خاصیت از دراپداون، مقدار خاصیت انتخابی را در یک برچسب نیز به صورت پویا نمایش خواهیم داد:
<select @bind="SelectedRoomPropValue">
@foreach (var prop in Room.RoomProps)
{
<option value="@prop.Value">@prop.Name</option>
}
</select>
<span>The value of the selected room prop is: @SelectedRoomPropValue</span>
@code
{
string SelectedRoomPropValue = "";
// ...
همانطور که مشاهده میکنید، انجام یک چنین کاری با Blazor بسیار سادهاست و نیازی به استفاده از جاوا اسکریپت و یا جیکوئری ندارد.
در اینجا یک فیلد را در قطعه کد برنامه تعریف کرده و به المان select متصل کردهایم. هرگاه آیتمی در این دراپ داون انتخاب شود، این فیلد، مقدار آن آیتم انتخابی را خواهد داشت. در ادامه توسط یک حلقهی foreach، تمام خواص یک اتاق را دریافت کرده و به صورت optionsهای یک select استاندارد، نمایش میدهیم. در آخر نیز مقدار SelectedRoomPropValue را نمایش دادهایم که این مقدار به صورت پویا تغییر میکند:
تعریف لیستی از اتاقها
عموما در یک برنامهی واقعی، با یک تک اتاق کار نمیکنیم. به همین جهت در ادامه لیستی از اتاقها را تعریف و مقدار دهی اولیه خواهیم کرد:
@code
{
string SelectedRoomPropValue = "";
List<BlazorRoom> Rooms = new List<BlazorRoom>();
protected override void OnInitialized()
{
base.OnInitialized();
Rooms.Add(new BlazorRoom
{
Id = 1,
Name = "Room 1",
IsActive = true,
Price = 499,
RoomProps = new List<BlazorRoomProp>
{
new BlazorRoomProp
{
Id = 1, Name = "Sq Ft", Value = "100"
},
new BlazorRoomProp
{
Id = 2, Name = "Occupancy", Value = "3"
}
}
});
Rooms.Add(new BlazorRoom
{
Id = 2,
Name = "Room 2",
IsActive = true,
Price = 399,
RoomProps = new List<BlazorRoomProp>
{
new BlazorRoomProp
{
Id = 1, Name = "Sq Ft", Value = "250"
},
new BlazorRoomProp
{
Id = 2, Name = "Occupancy", Value = "4"
}
}
});
}
}
در ابتدا فیلد Rooms تعریف شده که لیستی از BlazorRoomها است. در ادامه بجای مقدار دهی مستقیم آن در همان سطح قطعه کد، آنرا در یک متد life-cycle کامپوننت جاری به نام OnInitialized که مخصوص این نوع مقدار دهیهای اولیه است، مقدار دهی کردهایم.
نمایش لیست قابل ویرایش اتاقها
اکنون میخواهیم به عنوان تمرین 2، لیست جزئیات اتاقهای تعریف شده را نمایش دهیم؛ با این شرط که نام و قیمت هر اتاق، قابل ویرایش باشد. همچنین خواص تعریف شده نیز به صورت ستونهایی مجزا، نمایش داده شوند. برای مثال اگر دو خاصیت در اینجا تعریف شده، 2 ستون اضافهتر نیز برای نمایش آنها وجود داشته باشد. به علاوه از آنجائیکه میخواهیم اتصال دوطرفه را نیز آزمایش کنیم، نام و قیمت هر اتاق را نیز در پایین جدول، مجددا به صورت برچسبهایی نمایش خواهیم داد.
برای رسیدن به تصویر فوق میتوان به صورت زیر عمل کرد:
<div class="border p-2 mt-3">
<h2 class="text-info">Rooms List</h2>
<table class="table table-dark">
@foreach(var room in Rooms)
{
<tr>
<td>
<input type="text" @bind-value="room.Name" @bind-value:event="oninput"/>
</td>
<td>
<input type="text" @bind-value="room.Price" @bind-value:event="oninput"/>
</td>
@foreach (var roomProp in room.RoomProps)
{
<td>
@roomProp.Name, @roomProp.Value
</td>
}
</tr>
}
</table>
@foreach(var room in Rooms)
{
<p>@room.Name's price is @room.Price.</p>
}
</div>
در اینجا یک حلقهی تو در تو را مشاهده میکنید. حلقهی بیرونی، ردیفهای جدول را که شامل نام و قیمت هر اتاق است، به صورت input-boxهای متصل به خواص متناظر با آنها نمایش میدهد. سپس برای اینکه بتوانیم خواص هر ردیف را نیز نمایش دهیم، حلقهی دومی را بر روی room.RoomProps تشکیل دادهایم.
هدف از foreach پس از جدول، نمایش تغییرات انجام شدهی در input-boxها است. برای مثال اگر نام یک ردیف را تغییر دادیم، چون یک اتصال دو طرفه برقرار است، خاصیت متناظر با آن به روز رسانی شده و بلافاصله در برچسبهای ذیل جدول، منعکس میشود.
کدهای کامل این مطلب را از اینجا میتوانید دریافت کنید: Blazor-5x-Part-04.zip