devexpress blazor popup component not opening after closing - blazor-server-side

I created a popup component and added it to my page. but after turning it on and off it won't turn back on. I couldn't understand this problem
Component
<DxPopup
#bind-Visible="#WarningPopupVisible"
ShowFooter="true"
HeaderText="Warning"
BodyText="Content content content content"
>
<FooterContentTemplate>
<DxButton CssClass="popup-button" RenderStyle="ButtonRenderStyle.Primary" Text="Tamam" Click="#(() => WarningPopupVisible = false)"/>
</FooterContentTemplate>
</DxPopup>
#code {
[Parameter]
public bool WarningPopupVisible { get; set; }
}
/productspage
<Warning WarningPopupVisible="WarningPopupVisible"/>
<DxButton Text="Open Modal" CssClass="w-25" Click="#(() => WarningPopupVisible = true)" />
#code {
public bool WarningPopupVisible { get; set; }
}

it looks like you try to open an Popup from another class?
u need to reference to "WarningPopupVisible" dont initialize
"WarningPopupVisible" in /productpage again just use something like popup.WarningPopUpVisible = false;
and set the [Parameter] bool like this:
[Parameter]
public bool WarningPopupVisible { get; set; } = false;

Related

How to set input box value to a string in Blazor WebAssembly?

I am using Blazor WebAssmebly. I have an input box and I simply want to reset it to no text after the user types in text and hits the Enter key:
<input id="txtWord" name="txtWord" placeholder="Enter your text" #onchange="#onChange" #onkeyup="#Enter" />
private void onChange(Microsoft.AspNetCore.Components.ChangeEventArgs args)
{
value = (string)args.Value;
}
public void Enter(KeyboardEventArgs e)
{
if (e.Code == "Enter" || e.Code == "NumpadEnter")
{
if (value.Trim() != "")
{
doSomething();
}
}
}
So I set the variable 'value' to the input text, but then I want to clear the text box. How do I do that?
It looks like as if you're not binding your input to the value variable. Your code should be something like this:
<input id="txtWord" name="txtWord" placeholder="Enter your text"
value ="#value" #onchange="onChange" #onkeyup="Enter" />
#code
{
private string value;
}
Note that by adding the value attribute to the input element I create a two-way databinding, from the variable to the control, and from the control to the variable. When you use the #onchange directive, you create a one-way data binding.
In order to reset the input element, you can do the following:
if (value.Trim() != "")
{
// I guess it is here that you want to reset the input
// element. Assigning empty string to the `value` variable
// will cause the control to re-render with the new value;
// that is empty string...
value = "";
doSomething();
}
This will handle "enter" and "Submit" button press. I use this in a SignalR library I am developing. The default css classes are for Bootstrap.
SendBox.razor
<EditForm Model="#SendBoxViewModel" OnSubmit="#Send">
<div class="#DivClass">
<input #ref="#inputBox"
#bind-value="SendBoxViewModel.InputMessage"
#bind-value:event="oninput"
type="text"
aria-label="#Placeholder"
placeholder="#Placeholder"
class="#InputClass"
aria-describedby="button-send"
disabled=#Disabled>
<button class="#ButtonClass"
type="submit"
id="button-send"
disabled=#Disabled>
#Label
</button>
</div>
</EditForm>
SendBox.razor.cs
public partial class SendBox : ComponentBase
{
private ElementReference inputBox;
[Parameter]
public string Label { get; set; } = "Send";
[Parameter]
public string Placeholder { get; set; } = "Type a new message here.";
[Parameter]
public string DivClass { get; set; } = "input-group";
[Parameter]
public string InputClass { get; set; } = "form-control";
[Parameter]
public string ButtonClass { get; set; } = "btn btn-outline-primary";
[Parameter]
public bool Disabled { get; set; }
[Parameter]
public EventCallback<string> OnSend { get; set; }
public SendBoxViewModel SendBoxViewModel { get; set; } = new SendBoxViewModel();
private bool MessageInputInvalid => string.IsNullOrWhiteSpace(SendBoxViewModel.InputMessage);
private async Task Send()
{
if (!MessageInputInvalid)
{
await OnSend.InvokeAsync(SendBoxViewModel.InputMessage);
SendBoxViewModel.InputMessage = string.Empty;
await inputBox.FocusAsync();
}
}
}
SendBoxViewModel.cs
public class SendBoxViewModel
{
[MinLength(length: 1)]
[MaxLength(length: 1024)]
[Required(AllowEmptyStrings = false)]
public string? InputMessage { get; set; }
}

Validating Blazor sub-components?

I had a Blazor component containing a persons name and their address. I have split out the address so I can reuse it. I am using 2 way data binding between the person and address to ensure that data is passed to the address and the person can receive address changes.
I cannot get validation to work though. The person full name and the Address line 1 cannot be blank. When I use VaidationSummary then it correctly reports that both fields cannot be blank. But when I use ValidationMessage only the person full name reports a validation message. I am using Fluent validation but I believe the issue is that ValidationMessage does not report when in a complex type.
I think it is because the For() attribute for the Address line 1 ValidationMessage does not match the field name in the master form (Person) data model. The master data model has the address class as Address but the address component has it as Value. However, if I am to reuse the component then this is likely to happen!
Separating components like addresses seems a reasonable thing to do and you might have more than one address object on a form (delivery and billing for example) so I just need to know how to do it.
Has anyone done this? Is a custom ValidationMessage needed or a different For() implementation?
Thanks for your help with this. Here is the source.
Form:
<EditForm Model=#FormData>
<FluentValidator/>
<ValidationSummary/>
<InputText #bind-Value=FormData.FullName />
<ValidationMessage For="#(() => FormData.FullName)"/>
<ComponentAddress #bind-Value=FormData.Address />
<input type="submit" value="Submit" class="btn btn-primary" />
</EditForm>
#code{
PersonDataModel FormData = new PersonDataModel();
}
Address Component:
<InputText #bind-Value=Value.Address1 #onchange="UpdateValue" />
<ValidationMessage For="#(() => Value.Address1)" />
#code{
[Parameter] public AddressDataModel Value { get; set; }
[Parameter] public EventCallback<AddressDataModel> ValueChanged { get; set; }
protected async Task UpdateValue()
{
await ValueChanged.InvokeAsync(Value);
}
}
Person model:
public class PersonDataModel
{
[Required]
public string FullName { get; set; }
public AddressDataModel Address { get; set; }
public PersonDataModel()
{
Address = new AddressDataModel();
}
}
Address model:
public class AddressDataModel
{
[Required]
public string Address1 { get; set; }
}
Person Fluent Validator:
public class PersonValidator : AbstractValidator<PersonDataModel>
{
public PersonValidator()
{
RuleFor(r => r.FullName).NotEmpty().WithMessage("You must enter a name");
RuleFor(r => r.Address.Address1).NotEmpty().WithMessage("You must enter Address line 1");
}
}
The problem is that the ValidationContext for validating your Component is your Component's Value property - not the model that the parent page is using.
I struggled to get something to get component validation working until I figured out a bit of a hack using another Validation Attribute that I apply to the Component's Value property. When validating Value, I use the Component's EditContext which is a property that is set via a Cascading Parameter. I can get the property's name via reflection and that means I can get the correct FieldIdentifier, notifiy that the field has changed and then get the ValidationResults from the parent's EditContext. I can then return the same error details.
Validation Attribute
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Forms;
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Threading.Tasks;
using ValueBinding.Shared;
namespace ValueBinding.Data.Annotations
{
[AttributeUsage(AttributeTargets.Property, AllowMultiple = false, Inherited = true)]
public class MyValidationContextCheckAttribute : ValidationAttribute
{
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
EditContext ec = null;
string propName = "NOT SET";
// My StringWrapper is basic component with manual Value binding
if (validationContext.ObjectInstance is MyStringWrapper)
{
var strComp = (MyStringWrapper)validationContext.ObjectInstance;
ec = strComp.ParentEditContext; // Uses Cascading Value/Property
propName = strComp.GetPropertyName();
}
if (ec != null)
{
FieldIdentifier fld = ec.Field(propName);
ec.NotifyFieldChanged(in fld);
// Validation handled in Validation Context of the correct field not the "Value" Property on component
var errors = ec.GetValidationMessages(fld);
if (errors.Any())
{
string errorMessage = errors.First();
return new ValidationResult(errorMessage, new List<string> { propName });
}
else
{
return null;
}
}
else if (typeof(ComponentBase).IsAssignableFrom(validationContext.ObjectType))
{
return new ValidationResult($"{validationContext.MemberName} - Validation Context is Component and not data class", new List<string> { validationContext.MemberName });
}
else
{
return null;
}
}
}
}
Component
#using System.Linq.Expressions
#using System.Reflection
#using Data
#using Data.Annotations
<div class="fld" style="border-color: blue;">
<h3>#GetPropertyName()</h3>
<InputText #bind-Value=#Value />
<ValidationMessage For=#ValidationProperty />
<div class="fld-info">#HelpText</div>
</div>
#code {
[Parameter]
public string Label { get; set; } = "NOT SET";
[Parameter]
public string HelpText { get; set; } = "NOT SET";
[Parameter]
public Expression<Func<string>> ValidationProperty { get; set; }
private string stringValue = "NOT SET";
[MyValidationContextCheck]
[Parameter]
public string Value
{
get => stringValue;
set
{
if (!stringValue.Equals(value))
{
stringValue = value;
_ = ValueChanged.InvokeAsync(stringValue);
}
}
}
[Parameter]
public EventCallback<string> ValueChanged { get; set; }
[CascadingParameter]
public EditContext ParentEditContext { get; set; }
public string GetPropertyName()
{
Expression body = ValidationProperty.Body;
MemberExpression memberExpression = body as MemberExpression;
if (memberExpression == null)
{
memberExpression = (MemberExpression)((UnaryExpression)body).Operand;
}
PropertyInfo propInfo = memberExpression.Member as PropertyInfo;
return propInfo.Name;
}
}

Blazor's InputSelect Component not updating form validations errors on it's value change

I am using Blazor's InputSelect Component on a field called LocationId.
[Range(1, int.MaxValue, ErrorMessage = "Please Select Location")]
public int LocationId { get; set; }
On my razor component, where form validations are taking place, I am calling a child component like this:
<div class="form-group">
<label>Location</label>
<ValidationMessage For="#(() => StudentData.LocationId)" />
<SelectCommon RowType="Location" RowData="Locations" #bind-MyPhrase="#StudentData.LocationId">
<SelectOption>
<option selected disabled value="0">Choose a Location</option>
</SelectOption>
<OptionValue Context="p">
<option value="#p.Id">#p.City, #p.State</option>
</OptionValue>
</SelectCommon>
</div>
In the child component there is the InputSelect component whose code is:
#typeparam RowType
<InputSelect class="form-control" #bind-Value="HandleChange">
#if (SelectOption != null)
{
#SelectOption
}
#foreach (RowType item in RowData)
{
#OptionValue(item);
}
</InputSelect>
#code {
[Parameter]
public RenderFragment SelectOption { get; set; }
[Parameter]
public RenderFragment<RowType> OptionValue { get; set; }
[Parameter]
public IEnumerable<RowType> RowData { get; set; }
[Parameter]
public int MyPhrase { get; set; }
[Parameter]
public EventCallback<int> MyPhraseChanged { get; set; }
public int HandleChange
{
get { return MyPhrase; }
set
{
MyPhrase = value;
MyPhraseChanged.InvokeAsync(MyPhrase);
}
}
}
The work of #bind-Value="HandleChange" is to create blazor chain binding thing which is working perfectly. The parent component has this attribute #bind-MyPhrase="#StudentData.LocationId" which send the value of the model to the child for binding.
The problem is happening when i change the value of the select but the validation messages are not updated. However when I click the button which submits the form the validation messages updates. You can see the below gif which is showning this thing.
I have also notices that If id do not go with the chain binding approach and keep my InputSelect directly inside the EditForm component then this problem does not happen. It happens only in the parent-child way of coding (chain binding).
How can i correct this things?
Thanks to #dani-herrera
ApplicationUserDropDown.razor (child component):
<TelerikComboBox id="userbox" #bind-Value="#Value"
Data="#Data" TItem="UserListVM"
TValue="Guid?" Placeholder="Select User"
ValueField="Id" TextField="FullName">
ApplicationUserDropDown.razor.cs (child component):
public partial class ApplicationUserDropDown
{
#region Value two way binding
[CascadingParameter] EditContext EditContext { get; set; } = default!;
[Parameter] public Expression<Func<Guid?>> ValueExpression { get; set; }
private Guid? _valueId;
[Parameter]
public Guid? Value
{
get => _valueId;
set
{
if (_valueId == value) return;
_valueId = value;
ValueChanged.InvokeAsync(value);
var fieldIdentifier = FieldIdentifier.Create(ValueExpression);
EditContext.NotifyFieldChanged(fieldIdentifier);
}
}
[Parameter]
public EventCallback<Guid?> ValueChanged { get; set; }
#endregion Value two way binding
razor page using child component:
<ApplicationUserDropDown #bind-Value="Account.OwnerId"></ApplicationUserDropDown>
<ValidationMessage For="#(() => Account.OwnerId)">Owner is Required</ValidationMessage>

Strange popup behavior

I have a modal popup controller that I dynamically render on the page defined as follows
<MatDialog #bind-IsOpen="#Open">
<MatDialogTitle>#Title</MatDialogTitle>
<MatDialogContent>
#Content
</MatDialogContent>
<MatDialogActions>
<MatButton OnClick="#Yes">Yes</MatButton>
<MatButton OnClick="#(e => { Open = false; })">No</MatButton>
</MatDialogActions>
</MatDialog>
#code {
[Parameter] public bool Open { get; set; }
[Parameter] public string Title { get; set; }
[Parameter] public string Content { get; set; }
[Parameter] public EventCallback<string> YesCallback { get; set; }
void Yes()
{
Open = false;
YesCallback.InvokeAsync("deleted");
}
}
It just meant to close the popup and call the passed in callback function. Issue is if YesCallback.InvokeAsync("deleted"); is called dialog never closes. The callback gets executed but the dialog stays open. If YesCallback.InvokeAsync("deleted"); is removed the dialog closes fine, but, obviously no callback is called.
There are no errors.

What is the name convention of binding a complex ViewModels?

I have these two ViewModels
public class AboutViewModel : ViewModel
{
public override long Id { get; set; }
public override string PageTitle { get; set; }
public override string TitleDescription { get; set; }
public override string ContentTitle { get; set; }
public virtual AboutItemViewModel AboutItem { get; set; }
}
public class AboutItemViewModel
{
public long Id { get; set; }
[AllowHtml]
public string Content { get; set; }
public string ImageUrl { get; set; }
public HttpPostedFileBase FileToUpload { get; set; }
}
Here is my controller:
[ValidateInput(false)]
[ValidateAntiForgeryToken, HttpPost]
public ActionResult Create(long? siteid, long? cid, AboutViewModel model)
{
return View(model);
}
Here is my View:
#using (Html.BeginForm("Create", "About", new { siteid = ViewData["siteid"], cid = ViewData["cid"] },FormMethod.Post,new { enctype = "multipart/form-data", #class = "form-horizontal rtl", autocomplete = "off" }))
{
<div class="controls">
<input type="file" name="FileToUpload" id="FileToUpload" style="margin-right: -9px;">
</div>
<div class="controls">
#Html.ValidationMessageFor(o => o.AboutItem.FileToUpload, "", new { id = "spanfile", #class = "alert alert-block alert-error span3 pull-right", style = "margin-right: 160px;" })
</div>
<div class="control-group pull-left">
<button type="submit" class="btn btn-large" data-toggle="button">Save</button>
</div>
}
How to bind the file to FileToUpload to stop returning me a null?
Except:
If I put it in the main AboutViewModel than it's returns a correct value.
Since the FileToUpload property is in the AboutItem proprety, which is a class property of the parent ViewModel, you need to preface the name of your input element with the property it came from. That's a long way of saying that the name of your file input should be AboutItem.FileToUpload.
<input type="file" name="AboutItem.FileToUpload" id="AboutItem_FileToUpload" />
This should take care of the model binding for you. Additionally, you can test this by using an HTML helper on on of the other properties of the AboutItem class. For instance:
#Html.TextBoxFor(x=>x.AboutItem.Id)
This should render in the HTML
<input type="text" name="AboutItem.Id" id="AboutItem_Id />
EDIT
Apparently the id attribute will be rendered with an underscore instead of a dot. However, since the ID attribute is not used in model binding, it shouldn't really matter.