How to implement the inline editing for this grid using devexpress blazor - asp.net-core

I have a created a grid using devexpress blazor. I want to implement an inline editing for this grid, although devexpress haven't implemented any inline editing functionality.
Here is ht code i have used for this Grid.
#if (dischargeBoards == null)
{
<p><em>Loading...</em></p>
}
else
{
<div class="card demo-card-wide border-bottom-0">
<div class="card-header border-bottom-0">
ClientVisitGuid: <b>#(selectedRow == null ? (object)"(none)" : selectedRow.ClientVisitGuid)</b>
CurrentLocationGUID: <b>#(selectedRow == null ? (object)"(none)" : selectedRow.CurrentLocationGUID)</b>
PatientName: <b>#(selectedRow == null ? "(none)" : selectedRow.PatientName)</b>
</div>
</div>
<DxDataGrid Data="#dischargeBoards"
ShowFilterRow="false"
#bind-SingleSelectedDataRow="#SelectedRow"
        
        ShowPager="true"
        ShowGroupPanel="true"
        PageSize="19">
<DxDataGridColumn Field="#nameof(DischargeBoardVisit.LocationGroup)" Caption="L/C Group" AllowGroup="true"></DxDataGridColumn>
<DxDataGridColumn Field="#nameof(DischargeBoardVisit.Location)" Width="100px"></DxDataGridColumn>
<DxDataGridColumn Field="#nameof(DischargeBoardVisit.PatientName)" SortIndex="0" Width="240px"></DxDataGridColumn>
<DxDataGridColumn Field="#nameof(DischargeBoardVisit.Service)" Width="240px"></DxDataGridColumn>
<DxDataGridDateEditColumn Field="#nameof(DischargeBoardVisit.DischargeDateExp)"
                 DisplayFormatString="MM/dd/yyyy"
                 EditFormatString="d"></DxDataGridDateEditColumn>
<DxDataGridColumn Field="#nameof(DischargeBoardVisit.DischargeTimeExp)" Caption="Time Exp"></DxDataGridColumn>
<DxDataGridColumn Field="#nameof(DischargeBoardVisit.Family)" Caption="Family">
<DisplayTemplate>
#{
var checkboxFamily = (context as DischargeBoardVisit).Family;
if (checkboxFamily)
{
<button class="btn btn-success btn-circle btn-circle-sm m-1">#*<i class="fa fa-check"></i>*#</button>
}
else
{
<button class="btn btn-danger btn-circle btn-circle-sm m-1"></button>
}
}
</DisplayTemplate>
</DxDataGridColumn>
<DxDataGridColumn Field="#nameof(DischargeBoardVisit.Rehab)">
<DisplayTemplate>
#{
var checkboxRehab = (context as DischargeBoardVisit).Rehab;
//<input type="checkbox" checked="#checkboxRehab" disabled readonly />
if (checkboxRehab)
{
<button class="btn btn-success btn-circle btn-circle-sm m-1">#*<i class="fa fa-check"></i>*#</button>
}
else
{
<button class="btn btn-danger btn-circle btn-circle-sm m-1"></button>
}
}
</DisplayTemplate>
</DxDataGridColumn>
<DxDataGridColumn Field="#nameof(DischargeBoardVisit.Comment)" Width="240px"></DxDataGridColumn>
</DxDataGrid>
}
#code {
DischargeBoardVisit[] dischargeBoards;
// readonly string _id = Guid.NewGuid().ToString();
[Parameter]
public string DisplayFormatString { get; set; }
protected override async Task OnInitializedAsync()
{
dischargeBoards = await DischageBoard.GetDischargeAsync();
SelectedRow = dischargeBoards.FirstOrDefault();
}
public DischargeBoardVisit context { get; set; }
DischargeBoardVisit selectedRow;
public DischargeBoardVisit SelectedRow
{
get
{
return selectedRow;
}
set
{
selectedRow = value;
InvokeAsync(StateHasChanged);
}
}
}
I haved tried in different ways. But i couldn't find any proper solution for that.
thanks in advance. All appreciation would be highly appreciated.

Currently, there is no straightforward way to do so. That's why I suggest that you wait until this feature is implemented since it is in the Backlog of the DevExpress Blazor team. Please follow the updates in the blogs.

Related

Properties set to null when OnPost handler is called in razor page

I have a page with 2 buttons each one with their handler methods (OnPostAsync and OnPostResend) and 2 properties (LoginType and DocumentNumber) when OnPostAsync is called, properties have their values and the method works as expected but when the second button is clicked and OnPostResend is called properties are set to null. Why is this happenning and how can I prevent it?
This is the .cshtml file:
#page
#model SATCloudWebApp.Areas.Identity.Pages.Account.EnterOtpModel
#{
ViewData["Title"] = $"Ingrese el código de confirmación";
string device = TempData["PhoneNumber"]
}
}
<h2>#ViewData["Title"]</h2>
<div class="row">
<div class="col-md-6">
<h4>Por favor digite el código enviado a su #device. </h4>
<form method="post">
<div asp-validation-summary="All" class="text-danger"></div>
<div class="form-group">
<label asp-for="Input.OtpCode"></label>
<input asp-for="Input.OtpCode" class="form-control" />
<span asp-validation-for="Input.OtpCode" class="text-danger"></span>
</div>
<button type="submit" class="btn btn-info">Siguiente</button>
</form>
<href>
<form asp-page-handler="resend" method="post">
<button id="resendToken" type="submit" class="btn btn-dark">Reenviar código</button>
</form>
</href>
</div>
</div>
This is the page model:
namespace Name
{
[AllowAnonymous]
public class EnterOtpModel : PageModel
{
// constructor goes here ...
[BindProperty]
public string LoginType { get; set; }
[BindProperty]
public string DocumentNumber { get; set; }
[BindProperty]
public InputModel Input { get; set; }
public class InputModel
{
[Required(ErrorMessage = "Ingrese el código enviado.")]
[Display(Name = "Código de inicio de sesión")]
public string OtpCode { get; set; }
}
public IActionResult OnGet(string loginType, string documentNumber)
{
if (User.Identity.IsAuthenticated)
{
return RedirectToPage("~/LoginWithOtp");
}
else
{
LoginType = loginType;
List<SATCloudUser> _users = new List<SATCloudUser>();
_users = _userManager.Users.Where(x => x.DocumentNumber == documentNumber).ToList();
SATCloudUser _satUser = _users[0];
TempData["Username"] = _satUser.Email;
TempData["PhoneNumber"] = _satUser.PhoneNumber;
return Page();
}
}
public async Task<IActionResult> OnPostAsync(string returnUrl = null)
{
List<SATCloudUser> _users = new List<SATCloudUser>();
_users = _userManager.Users.Where(x => x.DocumentNumber == DocumentNumber).ToList();
SATCloudUser _satUser = _users[0];
if (ModelState.IsValid)
{
var result = await _userManager.VerifyTwoFactorTokenAsync(_satUser, "Email", Input.OtpCode);
if (result)
{
returnUrl = returnUrl ?? Url.Content("~/Home/Index");
var auth = await HttpContext.AuthenticateAsync(IdentityConstants.ApplicationScheme);
var authenticationMethod = auth?.Principal?.FindFirstValue(ClaimTypes.AuthenticationMethod);
await _signInManager.SignInAsync(_satUser, false, authenticationMethod);
return LocalRedirect(returnUrl);
}
else
{
TempData["Username"] = _satUser.Email;
TempData["PhoneNumber"] = _satUser.PhoneNumber;
TempData["messageEnterOtp"] = "InvalidToken";
return Page();
}
}
else
{
return Page();
}
}
public async void OnPostResendAsync()
{
List<SATCloudUser> _users = new List<SATCloudUser>();
_users = _userManager.Users.Where(x => x.DocumentNumber == DocumentNumber).ToList();
SATCloudUser _satUser = _users[0];
var token = await _userManager.GenerateTwoFactorTokenAsync(_satUser, "Email");
if(LoginType == "sms")
{
AlertsManager _alertManager = new AlertsManager();
string phoneNumber = "+57" + _satUser.PhoneNumber;
string message = $"Código de inicio de sesión en Better Together SE: {token}. Por favor no comparta este código.";
await _alertManager.SendTextMessageAsync(phoneNumber, message);
}
else if(LoginType == "email")
{
EmailManager _emailManager = new EmailManager();
await _emailManager.NewTokenEmail(_satUser.Email, token);
}
}
}
}
A new instance of the EnterOtpModel class is created for each request. So the state of its properties is not preserved between different requests. You can add hidden input elements inside the resend form so that LoginType and DocumentNumber are re-sent again to the EnterOtpModel.
<form asp-page-handler="resend" method="post">
<input type="hidden" asp-for="LoginType" />
<input type="hidden" asp-for="DocumentNumber" />
<button id="resendToken" type="submit" class="btn btn-dark">Reenviar código</button>
</form>

Blazor Server - How to disable a control inside component?

I try to show and disable a button by adding "disabled" class in a component (pre-hidden) but failed - it's not working. Where I did wrong ?
Component Button :
<div class="form-group">
<button class="btn btn-primary #disabled">Disable Me</button>
</div>
#code {
string _password;
string disabled = "";
public void Disable()
{
disabled = "disabled";
}
}
Index.razor :
#page "/"
<h1>How to disable button in component ?</h1>
<button class="btn btn-primary" #onclick="ShowAndDisableButton">Show and disable button</button>
#if (show)
{
<ButtonComponent #ref="button"/>
}
#code
{
ButtonComponent button = new();
bool show = false;
void ShowAndDisableButton()
{
show = true;
button.Disable();
}
}
UPDATED : if I change the ShowAndDisableButton code to
async Task ShowAndDisableButton()
{
show = true;
await Task.Delay(TimeSpan.FromMilliseconds(10)); // add this, wait for a while
button.Disable();
}
and change button code in index.razor to
<button class="btn btn-primary" #onclick="()=>ShowAndDisableButton()">Show and disable button</button>
it works. but I don't know why and don't want to use such way, are there any proper way?
The problem is that button.Disable(); does not cause the normal rerendering.
And it is an overcomoplicated way of doing things.
In the page:
#if (show)
{
<ButtonComponent Disabled="!show" />
}
#code
{
//ButtonComponent button = new();
bool show = false;
void ShowAndDisableButton()
{
show = true;
//button.Disable();
}
}
and the button itself:
<div class="form-group">
<button class="btn btn-primary" disabled="Disabled">Disable Me</button>
</div>
#code {
string _password;
// string disabled = "";
[Parameter] public bool Disabled {get; set; }
}
But you won't be able to use this button.
Use disabled="#disabledState"
Where disabledState is a boolean

Click event not working in Blazor while using JQuery Datatable

I have initialized Datatable in OnAfterRenderAsync and it's working fine
but onclick event doesn't work.
Grid.razor
<tbody>
#foreach (RecommendedActivityCreateViewModel model in activityList)
{
<tr>
<td><img src="#model.image_url" width="100" /></td>
<td>#model.title</td>
<td>#model.description</td>
<td>#model.created_at.ToString("dd-MMM-yyyy")</td>
<td nowrap="nowrap">
<button #onclick="(()=>EditData(model.Id))" data-toggle="modal" data-target="#AddEditEmpModal" class="btn btn-sm btn-clean btn-icon" title="Edit details">
<i class="la la-edit"></i>
</button>
<a class="btn btn-sm btn-clean btn-icon delete-link" data-url="api/RecommendedActivity/" data-id="#model.Id" title="Delete">
<i class="la la-trash"></i>
</a>
</td>
</tr>
}
</tbody>
GridModel.cs
public class GridModel : ComponentBase
{
[Inject]
protected HttpClient Http { get; set; }
protected async Task EditData(string id)
{
await Http.GetAsync($"api/RecommendedActivity/{id}");
}
}
While you say "I have initialized Datatable in OnAfterRenderAsync" the example below does that and if you copy, paste and run it you'll find nothing gets rendered. You've fetched the data after you've rendered the page. Get the data in OnInitializedAsync and it works.
You also say "and it's working fine but onclick event doesn't work." The EditData method should be in Grid.razor not GridModel which is where it appears to be in the code snippets provided.
The code below shows a simple working example (with GetData in OnInitializedAsync).
#page "/Test"
#foreach (var country in Countries)
{
<div>#country.Country <button class="btn-btn-dark" #onclick="() => OnClick(country.Id)">Edit</button></div>
}
#code {
class Model
{
public int Id { get; set; }
public string Country { get; set; }
}
private List<Model> Countries = new List<Model>();
protected Task OnClick(int id)
{
var x = id;
return Task.CompletedTask;
}
protected override Task OnInitializedAsync()
{
// GetData();
return base.OnInitializedAsync();
}
protected override Task OnAfterRenderAsync(bool firstRender)
{
GetData();
return base.OnAfterRenderAsync(firstRender);
}
protected void GetData()
{
Countries = new List<Model>()
{
new Model(){ Id=1, Country = "UK"},
new Model(){ Id=2, Country = "Spain"},
new Model(){ Id=3, Country = "Portugal"}
};
}
}

How to do two-way binding on a radio button Blazor child component

I'm trying to create a radio button component in Blazor that has two way data binding but I can't seem to get the value (testNumber) on the parent to change. I am new to Blazor and C#, so any help would be greatly appreciated. Thanks
ChildComp.razor
<div>
<label for="radio1">1</label>
<input
id="radio1"
type="radio"
value="1"
name="test"
checked="#(_selectedGroup == 1)"
#onclick="#(() => _selectedGroup = 1)"/>
<label for="radio2">2</label>
<input
id="radio2"
type="radio"
value="2"
name="test"
checked="#(_selectedGroup == 2)"
#onclick="#(() => _selectedGroup = 2)"/>
</div>
#code {
private int _selectedGroup = 0;
[Parameter]
public int BindingValue
{
get => _selectedGroup;
set
{
if (_selectedGroup == value )
{
return;
}
else
{
_selectedGroup = value;
}
BindingValueChanged.InvokeAsync(value);
}
}
[Parameter]
public EventCallback<int> BindingValueChanged { get; set; }
}
ParentComp.razor
<div>
<ChildComp #bind-BindingValue="testNumber" />
<h5>#testNumber</h5>
</div>
#code {
int testNumber = 0;
}
As you probably noticed bind does not work with radio buttons. On GitHub you can see the discussion about it here. It's sad that Blazor doesn't support these simple features...
I had the same problem and came across this solution:
#code {
public int EditionCode = 1;
// "Edition" class has "int Code" and "string Text" properties.
public Edition[] Editions = new [] {
new Edition { Code = 1, Text = "Home" },
new Edition { Code = 2, Text = "Pro" },
new Edition { Code = 3, Text = "Enterprise" },
};
}
#foreach (var edition in this.Editions)
{
<label>
<input type="radio"
name="edition"
checked="#(EditionCode == edition.Code)"
onchange="#(() => EditionCode = edition.Code)"/>
#edition.Text
</label>
}
If this solution doesn't solve your needs, then have a look here (a bit more complex).

Blazor server component isn't rerendered

I have a Blazor Server (.NETv5) application with a search page.
On this page I have a form to search by name.
On the form submit event I call the search method of my child component.
That component is doing the actual search. This is working fine.
Because the search might take a few seconds I want to show a spinner when the search starts and hide it when the search is done.
Also when I do a second search I want to hide the previous search results.
Hiding the spinner and showing the search results is working, but showing the spinner before the search doesn't work. The variable is set correctly but the page is not rerendered (I think).
My page:
<div class="container pt-2 mb-3">
<RadzenTemplateForm Data="#searchTerms" Submit="#((SearchTerms args) => { Submit(args); })">
<div class="row">
<div class="mb-2 col-6 pl-0">
<RadzenLabel Text="Name" />
<RadzenTextBox class="col-12" Name="Name" #bind-Value="searchTerms.Name"/>
</div>
</div>
<div class="row">
<div class="col-md-12 mt-3">
<RadzenButton ButtonType="ButtonType.Submit" Icon="search" Text="Search" Disabled="#isSearching" />
<RadzenButton ButtonStyle="ButtonStyle.Light" Icon="cancel" Text="Cancel" Click="#Cancel" class="ml-2"/>
</div>
</div>
</RadzenTemplateForm>
</div>
<SearchResultsComponent #ref="searchResultsComponent" />
protected SearchTerms searchTerms = new();
protected SearchResultsComponent searchResultsComponent;
protected bool isSearching;
protected void Submit(SearchTerms args)
{
if (string.IsNullOrEmpty(args.Name)) return;
// Disable submit button ==> NOT WORKING:
isSearching = true;
// Call search method on child component
searchResultsComponent.Search(args);
// Enable submit button again:
isSearching = false;
}
protected void Cancel()
{
// Reset form:
searchTerms = new SearchTerms();
}
My child component:
<div class="container">
#if (isSearching)
{
<div class="spinner-border text-primary mr-2" role="status">
<span class="sr-only">Searching...</span>
</div>
<b>Searching ...</b>
}
#if (noResults)
{
<div class="alert alert-warning" role="alert">
No results.
</div>
}
#if (getSearchResults != null && getSearchResults.Any())
{
<RadzenHeading Size="H2" Text=#($"Results({getSearchResults.Count})")></RadzenHeading>
<div class="row">
#foreach (var searchResult in getSearchResults)
{
<RadzenCard>
<b>#searchResult.Name</b>
</RadzenCard>
}
</div>
}
</div>
private IList<MultiShardSearchResultsWerknemer> _searchResults;
private bool _isSearching = true;
private bool _noResults;
protected bool noResults
{
get => _noResults;
set
{
if (Equals(_noResults, value)) return;
_noResults = value;
InvokeAsync(() => StateHasChanged());
}
}
protected bool isSearching
{
get => _isSearching;
set
{
if (Equals(_isSearching, value)) return;
_isSearching = value;
InvokeAsync(() => StateHasChanged());
}
}
protected IList<MultiShardSearchResultsWerknemer> getSearchResults
{
get => _searchResults;
set
{
if (Equals(_searchResults, value)) return;
_searchResults = value;
InvokeAsync(() => StateHasChanged());
}
}
public void Search(SearchTerms args)
{
Helpers.ConsoleLog(args);
if (string.IsNullOrEmpty(args.Name)) return;
// Reset ==> NOT WORKING:
isSearching = true;
noResults = false;
getSearchResults = null;
InvokeAsync(() => StateHasChanged());
getSearchResults = ShardingService.SearchForAllEmployees(args.Name, null).GetAwaiter().GetResult();
Helpers.ConsoleLog("Found results: " + getSearchResults.Count);
isSearching = false;
noResults = !getSearchResults.Any();
}
For debugging purposes, I've set _isSearching = true which shows me the spinner. The spinner is also hidden when the search is done, so that is working. But I can't get the spinner to show when I start searching.
I've tried all options I could find, without success.
I must be missing something. Please advice.
Have a look at your search handel method
protected void Submit(SearchTerms args)
{
if (string.IsNullOrEmpty(args.Name)) return;
// Disable submit button ==> NOT WORKING:
isSearching = true;
// Call search method on child component
searchResultsComponent.Search(args);
// Enable submit button again:
isSearching = false;
}
Keep in mind, that rendering will occur once the method has finished. So, before the call isSearching is false and after it is also false. That's why you don't see the spinner.
Blazor offers a method to kick off a new render cycle: StateHasChanged().
So, you could modify your submit method like
protected void Submit(SearchTerms args)
{
if (string.IsNullOrEmpty(args.Name)) return;
isSearching = true;
StateHasChanged()
// Call search method on child component
searchResultsComponent.Search(args);
isSearching = false;
StateHasChanged()
}
So, you click the search/submit button on this method is executed.
Or if you like, create a property instead
#code
{
private Boolean isSearching = false;
public Boolean IsSearching
{
get => isSearching;
private set
{
isSearching = value;
StateHasChanged();
}
}
protected void Submit(SearchTerms args)
{
if (string.IsNullOrEmpty(args.Name)) return;
IsSearching = true;
// Call search method on child component
searchResultsComponent.Search(args);
IsSearching = false;
}
}
I haven't tested it but faced a similar problems once.