در این قسمت میخواهیم بجای دریافت اطلاعات توضیحات یک اتاق، توسط یک text area متداول، برای مثال از Quill rich text editor استفاده کنیم. برای این منظور میتوان از کامپوننت Blazor محصور کنندهی آن به نام Blazored TextEditor کمک گرفت.
نصب کامپوننت Blazored TextEditor
ابتدا نیاز است بستهی نیوگت آنرا با اجرای دستور زیر، به پروژهی Blazor خود اضافه کرد:
و همچنین کتابخانهی اصلی quill را نیز در مسیر wwwroot/lib/quill نصب میکنیم:
سپس به فایل Pages\_Host.cshtml مراجعه کرده و ابتدا مداخل تعریف فایلهای CSS آنرا اضافه میکنیم:
و در ادامه سه مدخل اسکریپتی زیر را نیز به قسمت پیش از بسته شدن تگ body، اضافه میکنیم:
اگر برنامهی مورد نظر از نوع Blazor WASM است، این تنظیمات به فایل wwwroot\index.html منتقل میشوند.
و در آخر جهت سهولت کار با این کامپوننت میتوان فضای نام آنرا به فایل BlazorServer.App\_Imports.razor به صورت زیر اضافه کرد:
استفاده از کامپوننت Blazored.TextEditor در کامپوننت HotelRoomUpsert.razor
میخواهیم در کامپوننت HotelRoomUpsert.razor مثال این سری، بجای کامپوننت InputTextArea مورد استفاده، از یک HTML Editor استفاده کنیم:
- در اینجا قسمت محتوای EditorContent مثال آنرا خالی کردهایم.
- همانطور که ملاحظه میکنید، این تعریف به همراه یک ارجاع به وهلهای از آن نیز هست:
به همین جهت نیاز است فیلد متناظر با آنرا در قسمت کدهای کامپوننت، به صورت زیر تعریف کرد:
تا اینجا اگر برنامه را اجرا کنیم، به خروجی زیر میرسیم:
برای تغییر اندازه و مقدار placeholder پیشفرض آن، میتوان به صورت زیر عمل کرد:
تنظیم و دریافت متن نمایشی HTML Editor
مطابق مستندات این کامپوننت، روش تنظیم متن نمایشی آن، به کمک متد LoadHTMLContent است. به همین جهت متد زیر را به کدهای کامپوننت جاری اضافه میکنیم:
بنابراین روش متداول two-way binding در اینجا کار نمیکند و باید متن این ادیتور را به نحو فوق تنظیم کرد و برای مثال در زمان بارگذاری اولیهی این کامپوننت و در حالت ویرایش، متن دریافتی از بانک اطلاعاتی را به ادیتور فوق ارسال نمود:
و یا در زمان ثبت اولیه و یا حتی در حالت ویرایش اطلاعات در متد HandleHotelRoomUpsert، با استفاده از متد GetHTML آن، خاصیت HotelRoomModel.Details را مقدار دهی اولیه کرد:
مشکل! ادیتور در زمان ویرایش یک رکورد، اطلاعات پیشین را نمایش نمیدهد!
پس از اعمال تغییرات فوق، برنامه را اجرا میکنیم. سپس یک اتاق جدید را اضافه کرده و در لیست نمایش اتاقها، گزینهی ویرایش آنرا انتخاب میکنیم. در این حالت هرچند کار مقدار دهی HotelRoomModel.Details در زمان ثبت اطلاعات انجام شده، اما ... در زمان ویرایش چیزی نمایش داده نمیشود و تغییراتی را که به متد رویدادگردان OnInitializedAsync اضافه کردهایم، عمل نمیکنند.
در این مورد در قسمت بررسی چرخهی حیات کامپوننتها توضیحاتی ابتدایی ارائه شد:
«رویدادهای OnAfterRender و OnAfterRenderAsync
پس از هر بار رندر کامپوننت، این متدها فراخوانی میشوند. در این مرحله کار بارگذاری کامپوننت، دریافت اطلاعات و نمایش آنها به پایان رسیدهاست. یکی از کاربردهای آن، آغاز کامپوننتهای جاوا اسکریپتی است که برای کار، نیاز به DOM را دارند؛ مانند نمایش یک modal بوت استرپی.»
بنابراین در این حالت خاص که ادیتور جاوا اسکریپتی مورد استفاده، پس از رندر کامل UI نمایش داده میشود، قرار دادن متد SetHTML در روال رویدادگردان OnInitializedAsync کار نخواهد کرد و باید آنرا به روال رویدادگردان OnAfterRender انتقال دهیم:
پس از این تغییرات هم باز متن وارد شدهی در قسمت توضیحات، در حالت ویرایش نمایش داده نمیشود! علت آنرا نیز در مطلب بررسی چرخهی حیات کامپوننتها بررسی کردیم: «یک نکته: هر تغییری که در مقادیر فیلدها در این رویدادها صورت گیرند، به UI اعمال نمیشوند؛ چون در مرحلهی آخر رندر UI قرار دارند.» به همین جهت نیاز به فراخوانی دستی StateHasChanged وجود دارد:
مشکل! اگر در این حالت سعی کنیم متنی را در ادیتور وارد کنیم، میسر نیست و همچنین CPU Usage سیستم به 100 درصد رسیدهاست!
علت اینجا است که فراخوانی StateHasChanged، هر چند سبب رندر مجدد UI میشود، اما چون در پایان کار رندر قرار داریم، یک حلقهی بینهایت را سبب خواهد شد. به همین جهت باید در متد OnAfterRenderAsync، بر اساس پارامتر firstRender، از رندرهای بعدی جلوگیری کرد:
در اینجا هم مدیریت firstRender را مشاهده میکنید، تا دیگر یک حلقهی بینهایت رخ ندهد و هم حلقهای را جهت منتظر ماندن تا بارگذاری کامل Quill در این مثال. این افزونهی جاوا اسکریپتی، حتی پس از پایان رندر کامپوننت هم نیاز به مدت زمانی دارد تا بتواند کامل بارگذاری شده و قابل استفاده شود.
کدهای کامل این مطلب را از اینجا میتوانید دریافت کنید: Blazor-5x-Part-20.zip
نصب کامپوننت Blazored TextEditor
ابتدا نیاز است بستهی نیوگت آنرا با اجرای دستور زیر، به پروژهی Blazor خود اضافه کرد:
dotnet add package Blazored.TextEditor
libman install quill --provider unpkg --destination wwwroot/lib/quill
<head> <meta charset="utf-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>BlazorServer.App</title> <base href="~/" /> <link href="lib/quill/dist/quill.snow.css" rel="stylesheet" /> <link href="lib/quill/dist/quill.bubble.css" rel="stylesheet" />
<script src="lib/quill/dist/quill.min.js"></script> <script src="_content/Blazored.TextEditor/quill-blot-formatter.min.js"></script> <script src="_content/Blazored.TextEditor/Blazored-BlazorQuill.js"></script> <script src="_framework/blazor.server.js"></script> </body>
و در آخر جهت سهولت کار با این کامپوننت میتوان فضای نام آنرا به فایل BlazorServer.App\_Imports.razor به صورت زیر اضافه کرد:
@using Blazored.TextEditor
استفاده از کامپوننت Blazored.TextEditor در کامپوننت HotelRoomUpsert.razor
میخواهیم در کامپوننت HotelRoomUpsert.razor مثال این سری، بجای کامپوننت InputTextArea مورد استفاده، از یک HTML Editor استفاده کنیم:
<div class="form-group"> <label>Details</label> @*<InputTextArea @bind-Value="HotelRoomModel.Details" class="form-control"></InputTextArea>*@ <BlazoredTextEditor @ref="@QuillHtml"> <ToolbarContent> <select class="ql-header"> <option selected=""></option> <option value="1"></option> <option value="2"></option> <option value="3"></option> <option value="4"></option> <option value="5"></option> </select> <span class="ql-formats"> <button class="ql-bold"></button> <button class="ql-italic"></button> <button class="ql-underline"></button> <button class="ql-strike"></button> </span> <span class="ql-formats"> <select class="ql-color"></select> <select class="ql-background"></select> </span> <span class="ql-formats"> <button class="ql-list" value="ordered"></button> <button class="ql-list" value="bullet"></button> </span> <span class="ql-formats"> <button class="ql-link"></button> </span> </ToolbarContent> <EditorContent> </EditorContent> </BlazoredTextEditor> </div>
- همانطور که ملاحظه میکنید، این تعریف به همراه یک ارجاع به وهلهای از آن نیز هست:
<BlazoredTextEditor @ref="@QuillHtml">
@code { private BlazoredTextEditor QuillHtml;
برای تغییر اندازه و مقدار placeholder پیشفرض آن، میتوان به صورت زیر عمل کرد:
<div class="form-group pb-4" style="height:250px;"> <label>Details</label> <BlazoredTextEditor @ref="@QuillHtml" Placeholder="Please enter the room's detail">
تنظیم و دریافت متن نمایشی HTML Editor
مطابق مستندات این کامپوننت، روش تنظیم متن نمایشی آن، به کمک متد LoadHTMLContent است. به همین جهت متد زیر را به کدهای کامپوننت جاری اضافه میکنیم:
private async Task SetHTMLAsync() { if(!string.IsNullOrEmpty(HotelRoomModel.Details)) { await QuillHtml.LoadHTMLContent(HotelRoomModel.Details); } }
protected override async Task OnInitializedAsync() { if (Id.HasValue) { // Update Mode Title = "Update"; HotelRoomModel = await HotelRoomService.GetHotelRoomAsync(Id.Value); await SetHTMLAsync(); } // ... }
private async Task HandleHotelRoomUpsert() { // ... // Create Mode HotelRoomModel.Details = await QuillHtml.GetHTML(); // ... }
مشکل! ادیتور در زمان ویرایش یک رکورد، اطلاعات پیشین را نمایش نمیدهد!
پس از اعمال تغییرات فوق، برنامه را اجرا میکنیم. سپس یک اتاق جدید را اضافه کرده و در لیست نمایش اتاقها، گزینهی ویرایش آنرا انتخاب میکنیم. در این حالت هرچند کار مقدار دهی HotelRoomModel.Details در زمان ثبت اطلاعات انجام شده، اما ... در زمان ویرایش چیزی نمایش داده نمیشود و تغییراتی را که به متد رویدادگردان OnInitializedAsync اضافه کردهایم، عمل نمیکنند.
در این مورد در قسمت بررسی چرخهی حیات کامپوننتها توضیحاتی ابتدایی ارائه شد:
«رویدادهای OnAfterRender و OnAfterRenderAsync
پس از هر بار رندر کامپوننت، این متدها فراخوانی میشوند. در این مرحله کار بارگذاری کامپوننت، دریافت اطلاعات و نمایش آنها به پایان رسیدهاست. یکی از کاربردهای آن، آغاز کامپوننتهای جاوا اسکریپتی است که برای کار، نیاز به DOM را دارند؛ مانند نمایش یک modal بوت استرپی.»
بنابراین در این حالت خاص که ادیتور جاوا اسکریپتی مورد استفاده، پس از رندر کامل UI نمایش داده میشود، قرار دادن متد SetHTML در روال رویدادگردان OnInitializedAsync کار نخواهد کرد و باید آنرا به روال رویدادگردان OnAfterRender انتقال دهیم:
protected override async Task OnAfterRenderAsync(bool firstRender) { await SetHTMLAsync(); }
private async Task SetHTMLAsync() { if(!string.IsNullOrEmpty(HotelRoomModel.Details)) { await QuillHtml.LoadHTMLContent(HotelRoomModel.Details); StateHasChanged(); } }
مشکل! اگر در این حالت سعی کنیم متنی را در ادیتور وارد کنیم، میسر نیست و همچنین CPU Usage سیستم به 100 درصد رسیدهاست!
علت اینجا است که فراخوانی StateHasChanged، هر چند سبب رندر مجدد UI میشود، اما چون در پایان کار رندر قرار داریم، یک حلقهی بینهایت را سبب خواهد شد. به همین جهت باید در متد OnAfterRenderAsync، بر اساس پارامتر firstRender، از رندرهای بعدی جلوگیری کرد:
protected override async Task OnAfterRenderAsync(bool firstRender) { if (!firstRender) { return; } while (true) { try { await SetHTMLAsync(); break; } catch { await Task.Delay(100); // Quill needs some time to load } } }
کدهای کامل این مطلب را از اینجا میتوانید دریافت کنید: Blazor-5x-Part-20.zip