Blazor [Parameter] not update using #ref - properties

I'm using .net6.0 Blazor razor library to create a component.
#using Microsoft.JSInterop;
#inject IJSRuntime _jsRuntime;
<audio id="#Id" src="#Source" />
#code {
[Parameter]
public string Id { get; set; }
[Parameter]
public string Source { get; set; }
}
I use this component in a razor page using this :
<AudioPlayer
Id="reactorAudioElement1"
Source="/audio/lion-roaring.ogg">
</AudioPlayer>
And everything is doing fine at this point.
But, if I try to use #ref like that,
<AudioPlayer
#ref=#_Jukebox2>
</AudioPlayer>
#code {
private AudioPlayer _Jukebox2;
protected override void OnInitialized()
{
_Jukebox2 = new AudioPlayer()
{
Id="reactorAudioElement2",
Source="/audio/Bleep_02.ogg"
};
}
}
nothing is set in the DOM.
But I can read data like this, and I cannot set it...
<AudioPlayer
#ref=#_Jukebox2
Id="reactorAudioElement2"
</AudioPlayer>
#code {
private AudioPlayer _Jukebox2;
protected override void OnAfterRender(bool firstRender)
{
Console.WriteLine(_Jukebox2.Id); //ok
_Jukebox2.Source = "toto.mp3"; //doesn't do anything
}
}
Adding a StateHasChanged(); is not working
What am I doing wrong ?

Your component in DOM is created with this section of code, and your component will have valid #ref value
<AudioPlayer
#ref=#_Jukebox2
Id="reactorAudioElement2"
</AudioPlayer>
but you are creating another object in code with this section of code
_Jukebox2 = new AudioPlayer()
{
Id="reactorAudioElement2",
Source="/audio/Bleep_02.ogg"
};
In above code you can create object, but can not have DOM reference
This part of code will create an element in DOM and set it's reference to _Jukebox2, will also set value of Source
<AudioPlayer
#ref=#_Jukebox2
Id="reactorAudioElement2"
Source="#source"
</AudioPlayer>
private string source="Source="/audio/Bleep_02.ogg"`;
And in code behind you should be able to access public methods or variable in _Jukebox2 like this
_Jukebox.Source="toto.mp3";
If its not updating DOM then issue might be somewhere else

Related

how can I refer to a component created via DynamicComponent in Blazor?

I'm rendering components using DinamicComponent and I need to call a function found in the child component.
I can't find the equivalent of using #ref for the DinamicComponents so that I can reference to call the function.
This is the parent component
<div class="tab-content">
#foreach (VerticalTabComponent.TabModel oneTabItem in VerticalTabsList)
{
<div class="tab-pane fade show #(oneTabItem.TabIndex == SelectedTabIndex ? "active" : "")" #key=#($"VTabDivDynamic_{TabPrefix}_{oneTabItem.TabIndex.ToString()}")>
<DynamicComponent
Type=#System.Type.GetType(oneTabItem.TabComponent)
Parameters=#oneTabItem.TabParameters>
</DynamicComponent>
</div>
}
</div>
This is the code in Blazor Component Tab
public partial class TabComponent
{
[Parameter]
public EventCallback<string> InsertUpdateCallback { get; set; }
protected override async Task OnInitializedAsync()
{
await CallAnyfunctionAsync();
}
private async Task<bool> LoadDataGrid()
{
//this is the function I need to call from parent
}
}
How can I call the Load Grid function from the parent component?
There is an easy solution. Not sure if that is new but the ref-attribut does exist for the DynamicComponent! You can use it like this:
<DynamicComponent Type="typeof(MyComponent)" Parameters="#MyParameters" #ref="dc" />
and in Code-Behind:
private DynamicComponent? dc;
private MyComponent? MyComponentRef
{
get
{
return (MyComponent?)dc?.Instance;
}
}
Normally in Blazor we use #Ref to get a reference to a component, but as you've seen this won't work with a DynamicComponent.
A workaround for this would be to add a [Parameter] to the component called something like Register which is an action with the generic type set as the component type. You can then add code to handle OnParametersSet to call this method in the component.
You can then add a Register parameter in your TabParameters which gets updated with a reference.
Example code below would be added to the SurveyPrompt component:
/// <summary>
/// will be called when params set
/// </summary>
[Parameter] public Action<SurveyPrompt> Register { get; set; }
protected override void OnParametersSet()
{
if (Register != null)
{
// register this component
Register(this);
}
}
You add a Register parameter with an Action<type> value. Here's an example:
SurveyPrompt sp1 = null;
void Register1(SurveyPrompt survey)
{
sp1 = survey;
Console.WriteLine("SP1 has title " + sp1.Title);
}
protected override void OnInitialized()
{
Action<SurveyPrompt> p1 = Register1;
params1 = new Dictionary<string, object>()
{
{ "Title", "Survey Title Here" },
{ "Register", p1 }
};
}
IDictionary<string, object> params1;

passing blazor parameters to another page

I have been trying to pass parameters trough another page and this works, however i'm not getting what I desired and it has probably to do with what i pass.
The first thing i pass is a name but includes spaces and special character, the second thing i pass is a web link
how i send it:
<div class="col-sm-4">
<h3>Programming</h3>
#if (programming == null)
{
<p><em>Loading...</em></p>
}
else
{
foreach (var program in programming)
{
#program.Name
<br />
}
}
</div>
where it goes to
#page "/CourseDetails"
#using Portfolio.Models;
#using Portfolio_Frontend.Data;
#using Microsoft.AspNetCore.WebUtilities
#inject NavigationManager NavigationHelper
<h3>CourseDetails</h3>
#if (Name == null)
{
<p><em>Loading...</em></p>
}
else
{
<p>#Name</p>
}
#code {
public string Name { get; set; }
protected override void OnInitialized()
{
var uri = NavigationHelper.ToAbsoluteUri
(NavigationHelper.Uri);
if (QueryHelpers.ParseQuery(uri.Query).
TryGetValue("name", out var name))
{
Name = name.First();
}
}
}
i tried parameters as well and now tried query string gives the same result.
the name it should pass in this particular case is: C# Intermediate: Classes, Interfaces and OOP
What i get is only 'C' I assume because it is not able to translate the #.
is there a way to pass literal strings?
where it goes to: https://localhost:5105/CourseDetails/?name=C#%20Intermediate:%20Classes,%20Interfaces%20and%20OOP
this seems right to me.
Minor correction of URL syntax methodology
You have:
#program.Name
Which has a URL of /CourseDetails/?name=C#
Normally, you would do either
/CourseDetails/C#
/CourseDetails?name=C#
Except, Blazor doesn't explicitly support optional route parameters (/CourseDetails?name=C#)
REF: https://blazor-university.com/routing/optional-route-parameters/#:~:text=Optional%20route%20parameters%20aren%E2%80%99t%20supported%20explicitly%20by%20Blazor,,then%20replace%20all%20references%20to%20currentCount%20with%20CurrentCount.
It looks as though you can keep the optional query parameters and fiddle with the QueryHelpers.ParseQuery() I don't quite buy into that but if you want to keep going that route check out this post by #chris sainty
Link: https://chrissainty.com/working-with-query-strings-in-blazor/
I would much rather create a new model (DTO) that knows exactly how to display the CourseDetails name in a URL encoded fashion for the link, and the display name for the user.
public class ProgramModel
{
private readonly string name;
public ProgramModel(string name)
{
this.name = name;
}
public string DisplayName => name;
public string RelativeUrl => HttpUtility.UrlEncode(name);
}
And when we need to render the links on the 'Courses' page, it would look like this:
#page "/courses"
#using BlazorApp1.Data
<div class="col-sm-4">
<h3>Programming</h3>
#foreach (var program in programming)
{
#program.DisplayName
<br />
}
</div>
#code {
public IEnumerable<ProgramModel> programming { get; set; }
protected override void OnInitialized()
{
programming = new List<ProgramModel>()
{
new ProgramModel("Rust Things"),
new ProgramModel("JavaScript Things"),
new ProgramModel("C# Things")
};
}
}
And finally, when displaying the CourseDetails page, we can simply decode the name from the URL with the same utility that encoded the string in the first place, instead of guessing whether or not it's the apps fault, or the browsers fault that the '#' is not getting encoded properly to '%23'
#page "/CourseDetails/{Name}"
#inject NavigationManager NavigationHelper
#using System.Web
<h3>CourseDetails</h3>
<p>#HttpUtility.UrlDecode(Name)</p>
#code {
[Parameter]
public string Name { get; set; }
}
I recommend letting go of the idea of navigating from page to page, and using components:
<div>
#if (SelectedItem is not null)
{
<MyResultsPage SelectedProgramClass=#SelectedItem />
}
</div>
#code
{
ProgramClass SelectedItem {get; set;}
void SomeWayToSelectMyItem(ProgramClass newSelection){
SelectedItem = newSelection;
StateHasChanged();
}
}
Then in your display page, MyResultsPage.blazor
<div>
<div>#SelectedProgramClass.name</div>
. . .
</div>
#code {
[Parameter]
ProgramClass SelectedProgramClass{get; set;}
}
<MyResultsPage> will not show up in any way on the client, or even be initialized, until you've assigned something to SelectedProgramClass.

Convert input control to bind to multiple types - Blazor (.net v5)

Im attempting to create an input element that I can use inside AND outside an EditForm component as Chris Sainty explored in this excellent blog post.
The difference is I would like to create an input that can also be bound to any type.
The following code i tried for the child component:
#typeparam T
<input value="#Value" #oninput="HandleInput" />
#code {
private FieldIdentifier _fieldIdentifier;
[Parameter] public T Value { get; set; }
[Parameter] public EventCallback<T> ValueChanged { get; set; }
[Parameter] public Expression<Func<T>> ValueExpression { get; set; }
[CascadingParameter] private EditContext CascadedEditContext { get; set; }
protected override void OnInitialized()
{
_fieldIdentifier = FieldIdentifier.Create(ValueExpression);
}
private async Task HandleInput(ChangeEventArgs args)
{
#* How do I get args into a T type?
await ValueChanged.InvokeAsync(args.Value);*#
CascadedEditContext?.NotifyFieldChanged(_fieldIdentifier);
}
}
But what im struglling with is how can we convert args in the HandleInput function to type T?
Any ideas will be really appreciated!
Ok if this helps anyone else, ive tested a few types including nullables and replacing HandleInput with this works:
private async Task HandleInput(ChangeEventArgs args)
{
T argsValue = (T)TypeDescriptor.GetConverter(typeof(T)).ConvertFromInvariantString(args.Value.ToString());
await ValueChanged.InvokeAsync(argsValue);
CascadedEditContext?.NotifyFieldChanged(_fieldIdentifier);
}
Note - you will just need to handle errors converting types.

How to generate a Razor Page url within a custom TagHelper

I have a custom tag helper which should render something like this:
<ol>
<li>Some text
</ol>
If I were to do this within a Razor Page I would simply do something like this: <a asp-page="MyRazorPage">Some text</a>
Is there a way to do something similar inside of the TagHelper?
I found the answer.
Inject IUrlHelperFactory into the constructor as well as use the following property:
[HtmlAttributeNotBound]
[ViewContext]
public ViewContext ViewContext { get; set; }
Then you can create an IUrlHelper this way:
var urlHelper = _urlHelperFactory.GetUrlHelper(ViewContext);
var url = urlHelper.Page("/Clients/Edit", new { Id = myClientId });
output.Content.AppendHtmlLine($"<a href='{url}'>Edit</a>");
TagHelper provides HtmlTargetElement to add attributes to specified tags. Take adding asp-cuspage to the tag <a> as an example. The method Init is used to receive the parameters in the instruction asp-cuspage="". This method Process provides output attributes.
Create class CusAnchorTagHelper:
[HtmlTargetElement("a")]
public class CusAnchorTagHelper : TagHelper
{
private const string CuspageAttributeName = "asp-cuspage";
[HtmlAttributeName(CuspageAttributeName)]
public string Cuspage { get; set; }
public string Value { get; set; }
public override void Init(TagHelperContext context)
{
if (context.AllAttributes[0].Value != null)
{
Value = context.AllAttributes[0].Value.ToString();
}
base.Init(context);
}
public override void Process(TagHelperContext context, TagHelperOutput output)
{
var reg = new Regex("(?<!^)(?=[A-Z])");
string attr="";
foreach(var a in reg.Split(Value))
{
attr += a + "/";
}
output.Attributes.SetAttribute("href", attr);
}
}
Then, inject custom taghelper assembly into the page. And it will be drawn in the view.
This is the rendered result.

TagHelper PreContent and PostContent not showing up in HTML when overriding InputTagHelper

I want to create a tag helper to put some HTML before and after <input> tags, but I want to keep the default asp-for behavior and access the ModelExpression data.
To this end, I tried to override the Microsoft.AspNetCore.Mvc.TagHelpers.InputTagHelper type, as described in this post. However, even though I can verify in the debugger that the Process method is being called PreContent and PostContent are both being set, nothing shows up in the HTML other than the standard <input> tag. It works fine when creating a tag helper from scratch for another tag though.
I created a small project to demonstrate this issue. I put the entire project on GitHub, and I'm copying the specific tag helper I'm trying to create below.
[HtmlTargetElement("input", Attributes = "asp-for,test-label")]
public class TestTagHelper : InputTagHelper
{
public TestTagHelper(IHtmlGenerator generator) : base(generator)
{
}
[HtmlAttributeName("test-label")]
public string Label { get; set; }
public override void Process(TagHelperContext context, TagHelperOutput output)
{
output.PreContent.SetHtmlContent($"<b>{WebUtility.HtmlEncode(Label)}</b> ");
output.PostContent.SetHtmlContent($" <i>({WebUtility.HtmlEncode(For.Name)})</i>"); // access information from the input tag
base.Process(context, output);
}
}
Am I missing something obvious? Is there a caveat to doing this? Or is this something that just can't be done?
For this issue, we could try to call PreElement and PostElement instead of PreContent and PostContent like
[HtmlTargetElement("input", Attributes = "asp-for,test-label")]
public class TestTagHelper : InputTagHelper
{
public TestTagHelper(IHtmlGenerator generator) : base(generator)
{
}
[HtmlAttributeName("test-label")]
public string Label { get; set; }
public override void Process(TagHelperContext context, TagHelperOutput output)
{
base.Process(context, output);
output.PreElement.SetHtmlContent($"<b>{WebUtility.HtmlEncode(Label)}</b>");
output.PostElement.SetHtmlContent($"<i>({WebUtility.HtmlEncode(For.Name)})</i>");
}
}