I am learning about EventCallBack with blazor, but i wanted to know
Is there a way for my return method to return a second argument on the parent side. I would like to know the rowID that it came from
Parent Code
#foreach (var rw in pg.rows)
{
#if (rw.rowDef == 1)
{
#if (rw.widgetsSection1.Count == 0)
{
<AddWidgetToColumnComponent rowID="rw.rowID" onAddingWidget="AddWidgetToRow"></AddWidgetToColumnComponent>
}
}
}
}
#code{
...
private void AddWidgetToRow(GenericWidgets evnt)
{
//bool confirmed = await JsRuntime.InvokeAsync<bool>("confirm", "Are you sure?");
var row = pg.rows.Where(x => x.rowID == 3).FirstOrDefault();
//row.addWidgetSection1(widgets);
}
}
Child Component AddWidgetToColumnComponent
<button class="btn btn-info" #onclick="#addComponent">Ajouter</button>
#code {
[Parameter]
public int rowID { get; set; }
[Parameter]
public EventCallback<GenericWidgets> onAddingWidget { get; set; }
public async void addComponent()
{
GenericWidgets widget = new GenericWidgets();
await onAddingWidget.InvokeAsync(widget);
}
I am not sure how to accomplish this, if possible (debugger give me error). The only other way would be to change my generic class to add the rowID attribute in it. But is there an other way?
Change the lines ( what i would like it to be)
<AddWidgetToColumnComponent rowID="rw.rowID" onAddingWidget="AddWidgetToRow(rw.rowID)"></AddWidgetToColumnComponent>
and this line
private void AddWidgetToRow(GenericWidgets evnt, int rowID)
If the question is about how to get an EventCallback to fire on the parent with multiple parameters, I usually achieve this by creating a class to encapsulate everything that needs to get passed.
So you might end up with something like:
[Parameter] public EventCallback<AddWidgetArgs> onAddingWidget { get; set; }
And AddWidgetArgs is a class, like:
public class AddWidgetArgs
{
public GenericWidgets GenericWidget { get; set; } // My guess at your 1st parameter
public int RowId { get; set; } // Additional parameter
}
Then you consume it on the parent like this:
private void AddWidgetToRow(AddWidgetArgs args) { ... } // Or async Task, etc.
Related
I receive data from a SQL Server and load it into a model:
namespace BlazorServer.Models
{
public class Animal
{
public int height { get; set; }
public string name { get; set; }
public string origin { get; set; }
}
}
In a component I use the model to display data. The user is able to edit the data. Here is a sample:
<input #onchange="args => ValueChanged(args)" value="#animal.name" class="form-control form-control-sm border rounded" />
How do I get which property of the model has changed? I tried the following, but I only get the new value:
private void ValueChanged(ChangeEventArgs args)
{
var newValue = args.Value;
}
I want to update the model in the component (which equals the binding of blazor) AND also send the data to the SQL server right away.
Blazor comes with EditForm which manages an EditContext, and a set of Input controls - InputText in your case - that interface with EditContext. You can access the EditContext, register an event handler on OnFieldChanged and get change events. You get passed a FieldIdentifier that you can use to identify which field has been changed.
See - MS Documentation
Here's a simple razor page that demos using EditContext and OnFieldChanged
#page "/Test"
#implements IDisposable
<EditForm EditContext="this.editContext" class="m-3">
Animal: <InputText #bind-Value="this.model.name"></InputText><br />
Origin: <InputText #bind-Value="this.model.origin"></InputText><br />
</EditForm>
<div class="m-3">FieldChanged:<i>#this.FieldChanged</i> </div>
#code {
public class Animal
{
public int height { get; set; }
public string name { get; set; }
public string origin { get; set; }
}
private Animal model = new Animal() { height = 2, name = "giraffe", origin = "Africa" };
private EditContext editContext;
private string FieldChanged = "none";
protected override Task OnInitializedAsync()
{
this.editContext = new EditContext(model);
this.editContext.OnFieldChanged += this.OnFieldChanged;
return base.OnInitializedAsync();
}
private void OnFieldChanged(object sender, FieldChangedEventArgs e)
{
var x = e.FieldIdentifier;
this.FieldChanged = e.FieldIdentifier.FieldName;
}
// Need to Implement IDisosable to unhook event handler
public void Dispose ()
{
this.editContext.OnFieldChanged -= OnFieldChanged;
}
}
I want to return a list of links to a web page when it loads. Right now I have a model called SsoLink.cs bound to the page. I would like to return a list, so I have created another model called SsoLinks.cs that has a List. In my helper function, I keep getting "object not set to an instance of an object".
SsoLink.cs
public class SsoLink
{
public enum TypesOfLinks
{
[Display(Name="Please Select a Type")]
Types,
Collaboration,
[Display(Name="Backups & Storage")]
Backups_Storage,
Development,
[Display(Name="Cloud Services")]
Cloud_Services,
[Display(Name="Human Resources")]
Human_Resources,
Analytics
}
public string Id { get; set; }
public string Name { get; set; }
public string Url { get; set; }
public string Owner { get; set; }
public string OwnerEmail { get; set; }
public string LinkDescription { get; set; }
public TypesOfLinks LinkType { get; set; }
}
SsoLinks.cs
public class SsoLinks
{
public List<SsoLink> Links {get; set;}
}
GetLinksHelper.cs
public partial class SsoLinkHelper
{
public static SsoLinks GetLinks()
{
var ssoList = new SsoLinks();
try
{
//search the index for all sso entries
var searchResponse = _client.Search<SsoLink>(s => s
.Index(_ssoLinkIndex)
.Size(500)
.Query(q => q
.MatchAll()
)
);
if (searchResponse.Documents.Count == 0)
{
return ssoList;
}
ssoList.Links.AddRange(searchResponse.Hits.Select(hit => new SsoLink() {Id = hit.Source.Id, Name = hit.Source.Name, Url = hit.Source.Url, Owner = hit.Source.Owner}));
return ssoList;
}
catch (Exception e)
{
Log.Error(e, "Web.Helpers.SsoLinkHelper.GetLinks");
return ssoList;
}
}
}
While debugging, It is failing at SsoLinks.Links.AddRange(etc). How can I add a new SsoLink to the ssoList for every item found in my query?
Edit: Here is a screenshot of the error while debugging.
The null reference exception looks like it comes from ssoList.Links being null when calling AddRange on it, so it needs to be initialized to a new instance of List<SsoLink> before calling AddRange().
Russ's answer led me down the right path, I ended up just needing to change my view to:
#model List<SharedModels.Models.SsoLink>
rather than
#model SharedModels.Models.SsoLink
and do away with the SsoLinks model.
I am trying to bind a model field to a control, I have it working to the form but I'm trying to refactor the components so that I can reused the control and it's attributes throughout the program.
Here are some snippets to try to portray my situation:
Sample Model
public class MyModel
{
public DateTime DateOpened { get; set; }
}
Used in MyModelFormBase.cs
public class MyModelFormBase : ComponentBase
{
protected MyModal data = new MyModel();
}
Used in MyModelForm.razor
<MyForm Model="#data" AddFunction="#InsertMyModel" DataDismiss="MyModelForm">
<MyDateInput InputId="dateOpened" InputName="Date Opened" #bind-InputValue="#data.DateOpened" />
</MyForm>
MyForm.razor, uses blazorstrap
<BSForm Model="#Model" OnValidSubmit="#AddFunction"
#ChildContent
<BSButton Color="Color.Primary"
ButtonType="ButtonType.Submit"
data-dismiss="#DataDismiss">
Submit</BSButton>
</BSForm>
#code {
[Parameter]
public Object Model { get; set;}
[Parameter]
public RenderFragment ChildContent { get; set; }
[Parameter]
public EventCallback AddFunction { get; set; }
[Parameter]
public string DataDismiss { get; set; }
}
MyDateInput.razor, uses blazorstrap
<BSFormGroup>
<BSLabel For="#InputId">#InputName</BSLabel>
<BSInput InputType="InputType.Date" Id="#InputId"
#bind-Value="#InputValue"
ValidateOnChange="true"
#onchange="UpdateData"
/>
<BSFormFeedback For="#(() => InputValue)"/>
</BSFormGroup>
#code {
[Parameter]
public DateTime InputValue { get; set; }
[Parameter]
public EventCallback<DateTime> InputValueChanged { get; set; }
[Parameter]
public string InputId { get; set; }
[Parameter]
public string InputName { get; set; }
private async Task UpdateData()
{
await InputValueChanged.InvokeAsync(InputValue);
}
}
The default data provided by my service is correctly displayed in the control, so it's properly bound downwards, but doesn't propagate any changes back up to the model. :(
The goal is to be able to keep a MyDateInput Component that is repeated throughout the application that is bound 2 ways with the model regardless of layers of components it's passed through.
Any ideas?
It's really quite daunting to answer your question with so much code missing or unknown. However, I'll try to provide some assistance with the hope it can help you solve your ailments.
This code:
await InputValueChanged.InvokeAsync(InputValue);
I guess is a callback to update the value of the bound property MyModel.DateOpened , right ? If not it should be doing that. In any case, this property should be annotated with the PropertyAttribute attribute...
This may be the reason why two-way data binding is not working.
Note that your MyDateInput componet may also need to receive a ValueExpression value. I can't say that for sure, because not all your code is available, so take as a word of warning:
[Parameter] public Expression<Func<string>> ValueExpression { get; set; }
I'll put at the end of my answer a small sample to make this clear.
Are you sure this is working? The compiler should be barking:
#bind-Value="#InputValue" ValidateOnChange="true" #onchange="UpdateData"
telling you that you are using two onchange events. This should be, ordinarily
value="#InputValue" ValidateOnChange="true" #onchange="UpdateData", but I cannot be certain about it as I don't see how BSInput is implemented...
Note: BSForm is missing a closing tag before #ChildContent
Sample code:
RazorInputTextTest.razo
-----------------------
#page "/RazorInputTextTest"
<span>Name of the category: #category.Name</span>
<EditForm Model="#category">
<RazorInputText Value="#category.Name" ValueChanged="#OnValueChanged" ValueExpression="#(() => category.Name)" />
</EditForm>
#code{
private Category category { get; set; } = new Category { ID = "1", Name = "Beverages" };
private void OnValueChanged(string name)
{
category.Name = name;
}
}
RazorInputText.razor
--------------------
<div class="form-group">
<InputText class="form-control" #bind-Value="#Value"></InputText>
</div>
#code{
private string _value { get; set; }
[Parameter]
public string Value
{
get { return _value; }
set
{
if (_value != value) {
_value = value;
if (ValueChanged.HasDelegate)
{
ValueChanged.InvokeAsync(value);
}
}
}
}
[Parameter] public EventCallback<string> ValueChanged { get; set; }
[Parameter] public Expression<Func<string>> ValueExpression { get; set; }
}
Hope this helps...
I have my own Accordion code-only component
Here is my view where I have repeater which loads list of article sections. Each article section have list of articles. So with that I want to archieve that every article section will have his own accordion, which will contain articles. Thats why I have it in repeater
<div class="box box-primary">
<dot:Repeater DataSource="{{value: AccordionList}}">
<ItemTemplate>
<coc:Accordion DataSource="{{value: Articles}}"></coc:Accordion>
</ItemTemplate>
</dot:Repeater>
</div>
Accordion code-only component. My DataSource is always null even when I clearly see, that AccordionList contains List of Articles which is never null, but is never passed into my DataSource. When I change type of AccordionList to ArticleListDTOand pass it directly into my Accordion component, it worked well, but thats not what I want.
public class Accordion : HtmlGenericControl
{
public Accordion() : base("div")
{
}
public static readonly DotvvmProperty DataSourceProperty;
static Accordion()
{
DataSourceProperty = DotvvmProperty.Register<List<ArticleListDTO>, Accordion>(c=>c.DataSource);
}
//DataSource is always null
public List<ArticleListDTO> DataSource
{
get => (List<ArticleListDTO>)GetValue(DataSourceProperty);
set => SetValue(DataSourceProperty, value);
}
protected override void AddAttributesToRender(IHtmlWriter writer, IDotvvmRequestContext context)
{
Attributes.Add("class", "accordion");
base.AddAttributesToRender(writer, context);
}
public void DataBind(IDotvvmRequestContext context)
{
Children.Clear();
foreach (var item in DataSource)
{
DataBindItem(this, item, context);
}
}....etc
ViewModel
public List<ArticleSectionListDTO> AccordionList { get; set; } = new List<ArticleSectionListDTO>();
public List<ArticleSectionListDTO> AccordionListUnsorted { get; set; } = new List<ArticleSectionListDTO>();
protected override void OnItemLoading()
{
AccordionListUnsorted = Task.Run(() => articleSectionFacade.GetAllNotModifiedArticleSections()).Result;
AccordionList = Task.Run(() => articleSectionFacade.CreateTree(AccordionListUnsorted, null)).Result.ToList();
}
DTOs - I deleted rest of properties to make it clear
public class ArticleListDTO
{
public string Name { get; set; }
public int? ParentArticleId { get; set; }
public bool HasCategories => AssignedToArticle?.Count > 0;
public List<ArticleListDTO> AssignedToArticle { get; set; }
//Can contain sub articles
public List<ArticleListDTO> Articles { get; set; } = new List<ArticleListDTO>();
}
public class ArticleSectionListDTO : ListDTO
{
public string Name { get; set; }
public int? ParentArticleSectionId { get; set; }
public bool HasCategories => AssignedToMenuItem?.Count > 0;
public List<ArticleSectionListDTO> AssignedToMenuItem { get; set; }
public List<ArticleListDTO> Articles { get; set; } = new List<ArticleListDTO>();
}
The problem is that Repeater probably uses the client-rendering mode (it's the default). When it renders the HTML, it renders something like this:
<div data-bind="foreach: something">
<!-- template -->
</div>
When the template is rendered, its DataContext is null (becasue the template must not contain data from an item - it is a template).
So you have two options here:
Turn on server rendering by adding RenderSettings.Mode="Server" to the Repeater.
Update your control so it doesn't call DataBind when DataContext is null.
I'm trying to accomplish some hacking & slashing and was wondering how to approach the following.
There are 2 interfaces defined:
public interface IBase
{
string Name { get; }
void Run();
}
public interface ISecondBase<T> : IEntityTask
{
Thing<T> Thing { get; }
}
Somewhere else I have a list of IBase.
This list is filled ISecondBase. I would like to be able to loop through the list of Base, but using some reflection tricks and hacks als be able to call Thing on the items. I know they're there, the compiler doesn't.
So I'd have to cast it to its concrete type at runtime, but this cast has to be dynamic, based on reflected information in the loop... So all type information is dynamic... I'm starting to think in circles :)
Since I know on beforehand that everything inside it is always of the SecondBase type, I decided to use the dynamic keyword and just let it resolve at runtime. This seems to me like an easy way out. Is there some best practice for these cases? Should I redesign, without loss of generality, and how?
foreach(var x in y)
{
dynamic melp = x;
melp.Thingy;
}
Where to start?
Edit: Perhaps some more code to make the example less contrived.
I have the base classes as mentioned. In real life they look like this:
public interface IEntityTask
{
string Name { get; }
void Run();
}
public interface IEntityTask<T> : IEntityTask
{
Task<T> Task { get; }
}
//Then there are classes that implement these:
public class CreateEntityTask<T> : IEntityTask<Guid>
{
public T Entity { get; private set; }
public Func<T, Guid> EntityMethod { get; private set; }
public Task<Guid> Task { get; private set; }
public void Run()
{
Task = Task<Guid>.Run(() => entityAccess.CreateEntity<T>(Entity, EntityMethod));
}
}
public class ReadEntityTask<T> : IEntityTask<T>
{
public Guid EntityId { get; private set; }
public Func<Guid, T> EntityMethod { get; private set; }
public Task<T> Task { get; private set; }
public void Run()
{
Task = Task<T>.Run(() => entityAccess.ReadEntity<T>(EntityId, EntityMethod));
}
}
//Furthermore there is a class called EntityTaskManager, which holds a list of these things and runs, awaits & collects the results on them.
public class EntityTaskManager
{
public List<IEntityTask> EntityTasks { get; set; } // I want tasks of Guid and bool in here!!!!
public Dictionary<string, object> EntityTaskResults { get; set; }
}
In a calling class I construct a new EntityTask and add it to the list. And then call RunTasks on the manager.
I'd modify IEntityTask like this:
public interface IEntityTask
{
string Name { get; }
void Run();
object Result { get; }
}
If EntityTaskManager is the only place, where you work with IEntityTask type, the implementation of Result would be explicit:
public class CreateEntityTask<T> : IEntityTask<Guid>
{
/* The rest of code here */
object IEntityTask.Result
{
get { return Task.Result; }
}
}
Then fetching task results should be trivial:
var results = entityTasksManager
.EntityTasks
.Select(t => t.Result);