Blazor Component parameter should not be set outside of its component - asp.net

I am learning Blazor. I have no experience with component-based programming.
I have two components: a DateRangePicker and a RadzenCheckBox.
<RadzenFieldset Text="Test Component">
<DateRangePicker #ref="calendar" />
<div>
<Radzen.Blazor.RadzenCheckBox TValue="bool" Change="#((args) => txtBoxChange(args))" />
<RadzenLabel Text="Check" />
</div>
</RadzenFieldset>
Now, the requirement is simple. If the checkbox is clicked, show two calendars and show one calendar if it's unchecked.
I wrote the following code:
#code{
DateRangePicker calendar;
public void txtBoxChange(bool args)
{
if (args == true) //shows one calendar when checked
calendar.ShowOnlyOneCalendar = true;
else //shows two calendars when unchecked
calendar.ShowOnlyOneCalendar = false;
}
}
This works fine.
But I get a warning:
Component parameter 'ShowOnlyOneCalendar' should not be set outside of its component.
I have read some blogs about this warning, which suggest making parent and child component relationship for communication between components. But these are not parent and child.
What am I doing wrong?
What is the best way to achieve such a requirement and not have this warning?

What am I doing wrong?
Instead of using an imperative programming (component.Parameter1=v1) way, a Component Parameter is supposed be passed in declarative syntax :
<Component Parameter1="#v1" Parameter2="#v2" />
Note you're assigning values to [Parameter] directly:
calendar.ShowOnlyOneCalendar = true;
That's why Blaozr complains. In other words, you need change it in following way:
<DateRangePicker ShowOnlyOneCalendar="#showOnlyOne" />
How to fix
Always follow this pattern like other SPAļ¼š
(render)
Data -----------> View
For example, your code could be rewritten as below:
<DateRangePicker ShowOnlyOneCalendar="#flag" />
...
#code{
private bool flag = false;
public void txtBoxChange(bool args)=> flag = args;
}
(Here we have a flag data, and we should render the view according to the data)
Or if you do want to use an imperative programming way, you need to invoke a method and avoid assigning values to the [Parameter] properties directly.
<DateRangePicker #ref="calendar" />
...
#code{
DateRangePicker calendar;
public void txtBoxChange(bool args)
{
calendar.A_Wrapper_Method_That_Changes_The_Parameter(args);
// as suggested by #Uwe Keim,
// `InvokeAsync(StateHasChanged)` is better than `StateHasChanged()`
InvokeAsync(StateHasChanged);
}
}

Related

how do I bind a blazorise RichTextEdit component to a model property

I am attempting to use a blazorise RichTextEdit component within a form. I cannot seem to get the value to be set initially to the value of a provided model property.
<Form Model="#company">
<Validations #ref="validations" Mode="ValidationMode.Auto" ValidateOnLoad="false" Model="#model">
<Validation>
<Field>
<FieldLabel>Company Website</FieldLabel>
<TextEdit Role="TextRole.Url" #bind-Text="#model.Property1" Placeholder="Enter your website" Size="Size.Large">
<Feedback>
<ValidationError />
</Feedback>
</TextEdit>
</Field>
</Validation>
<Field>
<FieldLabel>About</FieldLabel>
<RichTextEdit #ref="richTextEditRef"
ContentChanged="#OnContentChanged"
Theme="RichTextEditTheme.Snow"
PlaceHolder="Tell us about the company..."
SubmitOnEnter="false"
ToolbarPosition="Placement.Top">
<Editor></Editor>
<Toolbar>
<RichTextEditToolbarGroup>
<RichTextEditToolbarButton Action="RichTextEditAction.Bold" />
<RichTextEditToolbarButton Action="RichTextEditAction.Italic" />
<RichTextEditToolbarSelect Action="RichTextEditAction.Size">
<RichTextEditToolbarSelectItem Value="small" />
<RichTextEditToolbarSelectItem Selected="true" />
<RichTextEditToolbarSelectItem Value="large" />
<RichTextEditToolbarSelectItem Value="huge">Very Big</RichTextEditToolbarSelectItem>
</RichTextEditToolbarSelect>
<RichTextEditToolbarButton Action="RichTextEditAction.List" Value="ordered" />
<RichTextEditToolbarButton Action="RichTextEditAction.List" Value="bullet" />
</RichTextEditToolbarGroup>
<!-- Custom toolbar content -->
<RichTextEditToolbarGroup Float="Float.Right">
</RichTextEditToolbarGroup>
</Toolbar>
</RichTextEdit>
</Field>
</Validations>
<Button Color="Color.Success" Clicked="#Submit">Save</Button>
</Form>
#code {
private Model model { get; set; } = new Model();
private RichTextEdit richTextEditRef;
Validations validations;
protected override async Task OnInitializedAsync()
{
model = await modelService.GetByAccount();
//await richTextEditRef.SetHtmlAsync(model.Property2);
}
public async Task OnContentChanged()
{
model.Property2 = await richTextEditRef.GetHtmlAsync();
}
async void Submit()
{
Console.WriteLine("Form Submitted");
var result = await modelService.Post(model);
}
}
The modelService only returns a single record, which id does successfully. I am able to retrieve the input value using richTextEditRef.GetHtmlAsync() however I cannot find a way to use the
richTextEditRef.SetHtmlAsync(company.About) method to initially set the value of the RichTextEdit.
I have tried calling it after calling the modelService as seen in the commented code, but this is inconstant as it is often excecuted prior to the service returning the record. I have also attempted overriding the OnAfterRenderAsync method but I am not sure I am doing that correctly.
Too much wasted time on this, please help!?
Well after much trial and error I got this to work. Hopefully someone else will benefit from this:
in the editor component add:
<Editor>#((MarkupString)model.Property2)</Editor>
in the #code add a new property:
public string newRichTextValue { get; set; }
in the OnContentChanged() method set the new property:
newRichTextValue = await richTextEditRef.GetHtmlAsync();
in the Submit() method set the model.Property2 value to the new property:
model.Property2 = newRichTextValue;
If the data is stored as an HTML string. You can render the html inside the
<Editor/> tag with the help of (MarkupString) See MarkupString
<Editor>#((MarkupString)HtmlString)</Editor>
This will initialize the plugin with data correctly formatted in the editor.

Blazor: binding to a MultiSelectList (ideally with a checkbox)

Experimenting with Blazor (Server, if that makes any difference), and I'm having difficulty getting binding to a MultiSelectList to work....
Bit of background: I'm dealing with EF Core and have a Many-to-Many relationship, let's say between people and cars. I'm currently loading a page that shows the existing details, and allowing the user to update this page.
So in my Service, I load my Person entity from the DB, and this includes the details of all the cars they currently own. I also load the list of all the available cars. My Service method then creates a MultiSelectList and adds it to my ViewModel (to be returned to the Razor Page):
Service method
vm.CarSelector = new MultiSelectList(
allCars,
nameof(Car.CarId),
nameof(Car.Name),
person.OwnedCars.Select(oc => oc.CarId));
This is fictitious code, but I hope you get the picture. When debugging this (in the Service method) I can see that this MultiSelectList has an entry for every car, and the ones that are already selected are showing as Selected. Great!
Blazor Razor Page
So, this is where I come unstuck.... I can't work out how to do the two-way data-binding of a Razor control to this object.
I'm trying to use an <InputSelect />, but that might not be the best control to use.
ideally (actually, that's more of a "must have"), each option should have CheckBox.
I'm wondering whether the use of a MultiSelectList really buys me anything
Checkboxes are a bit different in blazor. Normally you would use the bind-value attribute on an input element as shown below, however, this is not recommended as you will only be able to read the value and NOT update the UI by changing the boolean value via code:
<input type="checkbox" #bind-value="#item.Selected"/>
Instead, use the #bind syntax for checkboxes, which is much more robust and will work both ways (changing the bound boolean value from code & interacting with the checkbox on the UI). See the syntax below:
<input type="checkbox" #bind="#item.Selected"/>
The bind attribute will automatically bind your boolean value to the "checked" property of the html element.
Also make sure you are binding to the "Selected" property rather than the "Value" property.
Using the built in bind will prevent the need to manually setup events as you did in your answer. You can also get rid of the if/else block and merge your code into a single code flow since you are now binding to the boolean rather than setting the checked property manually. If you still need to tap into an event to fire off some process(maybe hiding parts of UI on checking a box), I'd suggest using the onclick event and manually passing in the multiselect Item for each line. Here is the final code:
#foreach(var item in list)
{
<input type="checkbox" #bind="item.Selected" #onclick="(()=>handleClick(item))" />
}
#foreach(var item in list.Where(x=>x.Selected))
{
<p> Item #item.Text is Selected</p>
}
#code {
MultiSelectList list = new MultiSelectList(new List<Car> { new Car { Year = 2019, Make = "Honda", Model = "Accord" }, new Car { Make = "Honda", Model = "Civic", Year = 2019 } });
private void handleClick(SelectListItem item)
{
//Do something crazy
}
}
I got this to work with a component that takes the MultiSelectList as a parameter. There may be more elegant ways to achieve this (please do update if you know of a better way).
#using Microsoft.AspNetCore.Components
#using Microsoft.AspNetCore.Mvc.Rendering
<div class="multiselect">
<div id="checkboxes">
#foreach (var item in this.Items)
{
<div>
<label for="#item.Value">
#if (item.Selected)
{
<input type="checkbox" id="#item.Value" checked="checked" #onchange="#((e) => CheckboxChanged(e, item.Value))" />
}
else
{
<input type="checkbox" id="#item.Value" #onchange="#((e) => CheckboxChanged(e, item.Value))" />
}
#item.Text
</label>
</div>
}
</div>
</div>
#code
{
[Parameter]
public MultiSelectList Items { get; set; } = null!;
private void CheckboxChanged(ChangeEventArgs e, string key)
{
var i = this.Items.FirstOrDefault(i => i.Value == key);
if (i != null)
{
i.Selected = (bool)e.Value;
}
}
}

Garbage Collector seems to cause the Button in my MahApps.Metro Custom Dialog to stop working

I am trying to write a simple custom dialog with one button to close/hide it. The code is mostly taken from the from the MahApps Examples Application with slight modifications. I am using a dialog service (called AutomationDialogService) to open the dialog from my ViewModel (see code below).
The problem I having, is that I am seeing weird behavior from my custom dialog. When I start my program, my button works correctly and executes the action to close the dialog as expected (see code). This works two to three times, if do it in quick succession, until it stops working. At that point, the dialog opens, but the button does not work any more. The point in time at which the button stops working seems to coincide with a run of the Garbage Collector (GC) as indicated in the Diagnostics Tools of Visual Studio.
Unfortunately I don't even know were to start looking/debugging. What I don't understand, is that my code is very similar to the code used in the .Net 4.5 CustomDialogExampleContent.cs, which pops up when you select "Show CustomDialog via VM" from the Dialogs drop-down menu of the example application include MahApps.Metro. But my code it does not work.
Therefore, the problem may be related to my usage of Dependency-Injection with StructureMap 4.3. Since this is a Plugin, I have put its registry into a separate file PluginRegistry. I also added the relevant entries as code snippets below. I should add, that I am using the same registry entries for my main DialogService, without any issues.
I would be grateful, if anyone could provide any pointer, as to what I could try to debug this strange issue.
After much talking, here is the code I have:
Dialog-related code in AutomationDialogService:
private CustomDialog automationRunningDialog;
private AutomationRunningDialogViewModel customDialogContent;
public async void OpenAutomationRunningDialog()
{
automationRunningDialog = new CustomDialog() { Title = "Automation Running" };
customDialogContent = new AutomationRunningDialogViewModel(instance =>
{
dialogCoordinator.HideMetroDialogAsync(parentViewModel, automationRunningDialog);
});
automationRunningDialog.Content = new AutomationRunningDialogView { DataContext = customDialogContent };
await dialogCoordinator.ShowMetroDialogAsync(parentViewModel, automationRunningDialog);
}
ViewModel of the AutomationDialog:
class AutomationRunningDialogViewModel : ViewModelBase
{
public AutomationRunningDialogViewModel(Action<AutomationRunningDialogViewModel> closeButtonHandler)
{
buttonCommand = new RelayCommand(() =>
{
closeButtonHandler(this);
});
}
private ICommand buttonCommand;
public ICommand ButtonCommand => buttonCommand;
}
And this is the corresponding AutomationRunningDialogView:
<UserControl x:Class="Extensions.Automation.Dialogs.AutomationRunningDialogView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:Extensions.Automation.Dialogs"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<Grid>
<Button Margin="0 8 0 8"
HorizontalAlignment="Right"
Command="{Binding ButtonCommand}">
OK
</Button>
</Grid>
</UserControl>
Code snipptes from PluginRegistry related to AutomationDialogService:
this.ForConcreteType<AutomationDialogService>()
.Configure
.Ctor<IDialogCoordinator>("dialogCoordinator").Is(c => c.GetInstance<DialogCoordinator>())
.Ctor<ViewModelBase>("parentViewModel").Is(c => c.GetInstance<MainWindowDialogParentViewModel>())
.Singleton();
this.For<IAutomationDialogService>()
.Use(c => c.GetInstance<AutomationDialogService>())
.Singleton();
Your RelayCommand action is accessing a closure (closeButtonHandler), which is being garbage collected:
buttonCommand = new RelayCommand(() =>
{
closeButtonHandler(this);
});
and therefore you need to use the keepTargetAlive parameter of RelayCommand:
buttonCommand = new RelayCommand(() =>
{
closeButtonHandler(this);
}, keepTargetAlive: true);
Further details here http://www.mvvmlight.net/doc/weakaction.cshtml
I thought my code was sufficiently similar to the MahApps-Example, but it was not. The MahApps example uses its own command SimpleCommand, while my code used the RelayCommand from MvvmLight.
I can eliminate the problem by replacing the following lines in AutomationRunningDialogViewModel:
buttonCommand = new RelayCommand(() =>
{
closeButtonHandler(this);
});
with these:
buttonCommand = new SimpleCommand
{
ExecuteDelegate = o => closeButtonHandler(this)
};
The main difference seems to be the signature of the command delegate-action between these two commands. For ExecuteDelegate in SimpleCommand it is:
public Action<object> ExecuteDelegate { get; set; }
where as for the ctor of RelayCommand it is:
public RelayCommand(Action execute);
So there is an obvious difference. But I don't understand, why this causes the problematic behavior with RelayCommand mentioned above, and if there is some way to continue using RelayCommand instead of SimpleCommand.
I would be grateful for comments!

flex: How to respond to change of data inside a component

I've created a custom component based on Image component. I want to teach it to respond to change of binded variable. E.g. if main class has a variable balance, I want the component to change image in case balance = 100 one image, in case balance = 50 to another.
Can somebody help to understand how to do that
I am using flex3 so don't see propertyWatcner there. Also I am using component2 from main mxml file this way
<MyComp:MyIcon left="15" top="20" width="60" height="60"
id="tower" price="100" accountState="{accountMoney}"
click="drawBuildingShadow(event)" />
And inside component MyIcon I want to be able to react changes of binded accountState variable.
Without any code to go by (please include a sample of both components if you can), there are a number of ways you could approach this.
You could add a ChangeWatcher which is bound to the variable in question, and invokes a method when the property changes. This method could change the image, based on whatever conditions apply to the property.
It would look something like this:
Component 1:
[Bindable]
public var yourVariable:Number;
Component 2:
private var propertyWatcher:ChangeWatcher;
//In some initialization method -- this is JUST an example method for how to create the property watcher
public function init(): void {
...
propertyWatcher = ChangeWatcher.watch(component1, "yourVariable", onVariableUpdate);
}
//Define this as a new method to handle when the property changes
private function onVariableUpdate(event:PropertyChangeEvent):void {
if(event.newValue == 50) {
yourImage.source = newSource;
}
else if(event.newValue == 100) {
yourImage.source = otherSource;
}
}
Obviously, this is very truncated and shorthand, but hopefully it will help get you started.
Edit: ChangeWatchers do exist in Flex 3, but it sounds like you should go in a different direction. Since the code snippet you posted is a bit small, I'm going to make a few assumptions on how you might do this :)
As alxx mentioned in his comment, you can change the property accountState in your component from an actual property, to a setter/getter. This will allow you to do more extensive processing when accountState gets updated.
It should look something like this:
MyComp:
//Inside your script tag:
private var _accountState:Number;
[Bindable]
public function get accountState():Number {
return _accountState;
}
public function set accountState(state:Number):void {
_accountState = state;
switch(state) {
case 50:
yourIcon.source = "blahblahblah";
break;
case 100:
yourIcon.source = "blahblahblah2";
break;
//And so on
}
}
This won't change the code you posted: it should still work as you've written it. I haven't tested this, so it's possible I'm missing something, but hopefully this will help :)

Flex: Special-casing an item in a list or menu?

I've found it's often useful to special case the first item in a drop-down menu (ie, an instance of Menu). For example, if I want to pick a color from the list provided by a web service:
<mx:PopUpMenuButton id="colorSelelector"
dataProvider="{colorsService.lastResult}" />
I might also want a special-case, which is "enter a new color", allowing the user to enter the RGB values for a new color which isn't in the list. For example:
var newColor = { label: "Enter a new color", rgb: null };
Then used with:
<mx:PopUpMenuButton id="colorSelelector"
dataProvider="{colorsService.lastResult}"
lastOption="{newColor}" />
So, apart from changing the list I get back from the service, is there any better way to do this?
(and just a preemptive comment: this is a simplificationā€¦ I'm not actually trying to make a color-picking-list)
When you bind to the dataProvider, call a function that adds your special case. For instance:
<mx:PopUpMenuButton id="colorSelector"
dataProvider="{addSpecialCases(colorsService.lastResult)}"/>
So, apart from changing the list I get
back from the service, is there any
better way to do this?
This approach is going to be the cleanest, without extending HTTPService, which would work well (but is really just altering your result ;) ):
package
{
import mx.rpc.http.HTTPService;
public class MyHTTPService extends HTTPService
{
public var appendToResult:Object;
public function MyHTTPService(rootURL:String=null, destination:String=null)
{
super(rootURL, destination);
}
[Bindable("resultForBinding")]
override public function get lastResult():Object
{
//I know what my type is, Array as an example
var myResult:Array = operation.lastResult;
myResult.push( this.appendToResult )
return myResult;
}
}
}

Resources