I work on a project that uses Blazer for the front-end and Cosmos DB in the back-end. I am looping through a list of items and have a button/ anchor tag that OnClick receives a parameter. Currently, I am using the query string parameters to redirect the user from one page to another page. However, I do not want to use query string. I want to make the component non-routable and load it on a button click. Is there a way to pass the parameter on button click and load a non-routable component?
Thank you very much for your explanation, I am using .net 5. Here I am going to write a sample code, so you could help.
My project uses Azure Cosmos DB and Blazor Server.
<!-- begin snippet: js hide: false console: true babel: false -->
<!-- language: lang-html -->
///////////////////////////
MoviesListComponent.razor
////////////////////////
#page "/movies"
foreach(movie in ListOfMovies){
<div> movie.ReleaseeDate</div>
<div> movie.Country</div>
<div> movie.Title</div>
<button type="button" #onClick="()=>LoadAnotherComponent(movie.title)"> on button click redirect to(or load) a non-routable compnent and send movie.title as parameter for example MovieTitleAnalysis component</button>
}
#code{
public List<Movie> ListOfMovies {get;set;}
}
on button click inside MoviesListComponent load
MovieTitleAnalysis.razor and pass the parameter. Dispose MovieListComponent
///////////////////////
MovieTitleAnalysis.razor is a non routable component
//////////////////
<div>welcome to movie Title analysis</div>
#code{
[paramter]
public string movieTitle {get;set;}
void PreformSomeOpration(){}
}
As this is conceptual - you have provided no code context - this should provide a good starting point and demo the basic concept. Comment on how well/poorly it fits!
I've pointed to the "routed" components but it will work with any component.
DynamicPage.razor
#if (ComponentType != null)
{
<DynamicComponent Parameters=this.Parameters Type=this.ComponentType />
}
else
{
#this.ChildContent
}
#code {
[Parameter] public Type? ComponentType { get; set; }
[Parameter] public RenderFragment? ChildContent { get; set; }
[Parameter] public IDictionary<string, object>? Parameters { get; set; }
}
And Index.razor
#page "/"
#*Example Menu*#
<div class="p-2 m-2">
<button class="btn btn-primary" #onclick="() => this.ChangePage(null)">Change to Index</button>
<button class="btn btn-secondary" #onclick="() => this.ChangePage(typeof(Counter))">Change to Counter</button>
<button class="btn btn-info" #onclick="() => this.ChangePage(typeof(FetchData))">Change to FetchData</button>
</div>
<DynamicPage ComponentType=this.page>
<PageTitle>Index</PageTitle>
<div>Hello World from Blazor</div>
</DynamicPage>
#code {
private Type? page = null;
private void ChangePage(Type? newPage)
=> this.page = newPage;
}
Related
I have a <select> element in my page, of which I want to get the value. To do that, I've added the #bind= property. Here's the code:
<form target="#">
<label for="subject">Vak</label>
<select #bind="#selectedSubject" id="subject">
#foreach (Subject subject in Auth.User.Subjects)
{
<option value="#subject.Uuid">#subject.Name</option>
}
</select><br />
<!-- (more input fields) -->
<button class="button btn-primary mt-3" #onclick="addNote">Toevoegen</button>
</form>
#code {
private String selectedSubject { get; set; }
private String inputTitle { get; set; }
private String inputContent { get; set; }
private void addNote()
{
Console.WriteLine(selectedSubject);
}
}
This doesn't output anything, and trying to use selectedSubject results in a NullReferenceError.
The other values (inputTitle, inputContent) work as expected.
Yes, the <select> is properly being filled with <option>s.
I've tried switching to whatever <EditForm> is, but it kept giving me warnings and failed to build.
First of all, you don't need a form at all. The main purpose of EditForm is to validate inputs-- like, making sure a password is the correct format.
Also, unless you want to programmatically SET the value of the dropdown, you don't need to bind to it-- instead, use #onchange:
<select #onchange=ProcessSelection>
. . .
#code {
Subject SelectedSubject {get; set;}
async Task ProcessSelection (ChangeEventArgs args){
SelectedSubject = Auth.User.Subjects.Single(s=>s.uuid = args.Value.ToString();
// Process the selection.
}
}
This will (1) give you a place to breakpoint for debugging. (2) Let you do useful things with the SelectedSubject object more easily (like add / remove it from a list, pass it as a parameter to a display component, etc.).
My approach to the problem is different from the other answer.
Here's everything in one Razor component.
Separate your data out into a class. Note that the fields can be null (you may not want them to be).
Use EditForm with normal binding. You can then do validation.
Logic in select list to determine if no value is selected and display a select message.
Separate out the Select code into a separate RenderFragment block to keep the code cleaner. Not necessary if you like all your markup in one place.
#page "/"
<PageTitle>Index</PageTitle>
<h1>Hello, world!</h1>
<EditForm EditContext="editContext">
<div class="col-12 mb-3">
<label class="form-label small muted">Subject</label>
<select class="form-select" #bind="model.Subject">
#this.SubjectList
</select>
</div>
<div class="col-12 mb-3">
<label class="form-label small muted">Title</label>
<input class="form-control" #bind="model.Title">
</div>
</EditForm>
<div class="alert alert-info m-3">
<div class="m-2">
Subject : #model.Subject
</div>
<div class="m-2">
Title : #model.Title
</div>
<div class="m-2">
Content : #model.Content
</div>
</div>
#code {
//private Note model = new() { Subject="Portugal"};
private Note model = new();
private EditContext? editContext;
protected override void OnInitialized()
=> this.editContext = new EditContext(model);
private IEnumerable<string> Subjects = new List<string> { "France", "Portugal", "Spain" };
private bool noSubjectSelected => !Subjects.Any(item => item == model.Subject);
// Separates out the code into a separate render block
private RenderFragment SubjectList => (__builder) =>
{
if (noSubjectSelected)
{
<option disabled selected value="">-- Select a Subject --</option>
}
foreach (var subject in Subjects)
{
<option value="#subject">#subject</option>
}
};
// Normally separate out into own class file
public class Note
{
public string? Subject { get; set; }
public string? Title { get; set; }
public string? Content { get; set; }
}
}
I've left the subject as a string as that's what you have it as. If it's a guid you will need to adjust the Select Code to handle a Guid/String pair.
Bear in mind that source code is an instruction of rendering intent so it isn't always clear how your code will react in all cases of operation.
You have to decide whether there will be a selection at all times, or whether no material selection is also valid. For example, if you want an option of "Select a Subject" at the top of the list which is the default, so set am option with that text an a value of Guid.Empty (all zeros). When processing values - just check that the selected Guid is not equal to Guid.Empty if you want to know if a "real" subject option is selected
I use the following two approaches with InputSelect. Note that the Microsoft renderer may set the text of the select to your bound option but may not set that option to "selected"
<InputSelect #bind-Value=MyData>
<option selected value="#Guid.Empty">Select a Subject</option>
#foreach (var i in Subjects)
{
<option value=#i.Id>#i.SubjectName</option>
}
</InputSelect>
If you want finer grained control over the select, there is this binding option. As well as a value to which you bind, you also need a function which is called on change - SelectChanged - where you can run other code. you should set the bound value within this method. If the InputSelect is inside a component, the bound value should also have an EventHandler of the same name with the "Changed" text appended - you should Invoke this in your SelectChanged function. Any parent control binding to the child will have its data automatically updated and the SetStateChanged is called for you
<InputSelect Value="#MyValue" ValueChanged="#((Guid value) => SelectChanged(value))" ValueExpression="#(() => MyValue)" >
#foreach (var i in MyValues)
{
if (TheSelectedValue == i.TheValue)
{
<option selected value=#i.TheValue>#i.ValueDisplay</option>
}
else
{
<option value=#i.TheValue>#i.ValueDisplay</option>
}
}
</InputSelect>
To solve your issue with things not changing, there are two ways you can do it:
Start with a null object for your SelectedSubject, do a null check in your click, and have a dummy select option that forces you to select an item:
.
<select #onchange=ProcessChange>
<option selected disabled>Pick a subject. . . </option>
#foreach (var subject in Auth.User.Subjects) {
<option>. . . </option>
}
</select>
<button #onclick=AddSubject />
#code {
List<Subject> SelectedSubjects = new();
Subject SelectedSubject {get; set; } = null;
async Task ProcessChange (ChangeEventArgs args){. . . }
async Task AddSubject{
if (Subject is not null) SelectedSubjects.Add (SelectedSubject);
}
}
Preselect your first option in your data initialization, and then you don't NEED the dummy <option> or the null check.
.
#code{
async Task OnInitializedAsync (bool IsFirstRender){
Auth.User.Subjects = await LoadSubjectsForUser (Auth.User);
SelectedSubject = Auth.User.Subjects.First();
}
}
In your use case, I think #1 is probably better.
To add some context, I'm trying to create a Dropdown select Blazor component. I've managed to create a concept of this entirely with CSS, #onclick, and #onfocusout.
I'm trying to pass a reference of the DropDown component to its children, DropDownItem. The only way I know how to achieve this, is by using the #ref and passing it as a parameter to the DropDownItem component.
<DropDown #ref="DropDownReference">
<DropDownItem ParentDropDown=#DropDownReference>Hello</DropDownItem>
<DropDownItem ParentDropDown=#DropDownReference>World</DropDownItem>
</DropDown>
There has to be a cleaner approach here that does not require manually passing the reference down to each child instance. I suppose I could use CascadingValue but that will still require me to store the DropDown reference.
I'm trying to notify DropDown parent when a click event occurs in DropDownItem. This will signal the parent to changes it selected value - as it would traditionally work in a select.
Here is an example of how you could do it using CascadingValue. The DropDownItem component will accept a [CascadingParameter] of type DropDown. There is nothing wrong in doing that, this is how it's done in most (if not all) component libraries.
DropDown.razor
<CascadingValue Value="this" IsFixed="true">
#* Dropdown code *#
<div class="dropdown">
#ChildContent
</div>
</CascadingValue>
#code {
[Parameter] public RenderFragment ChildContent { get; set; }
private string selectedItem;
public void SelectItem(string item)
{
selectedItem = item;
StateHasChanged();
}
}
DropDownItem.razor
#* Dropdown item code *#
<div class="dropdown-item" #onclick="OnItemClick">...</div>
#code {
[CascadingParameter] public DropDown ParentDropDown { get; set; }
[Parameter] public string Name { get; set; }
private void OnItemClick()
{
ParentDropDown.SelectItem(Name);
}
}
Usage:
<DropDown>
<DropDownItem Name="Hello">Hello</DropDownItem>
<DropDownItem Name="World">World</DropDownItem>
</DropDown>
I am working on a Blazor server project and created this modal window. This is a component itself
<div class="modal #ModalClass" tabindex="-1" role="dialog" style="display:#ModalDisplay">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">Modal title</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close" #onclick="() => Close()">
<span aria-hidden="true">×</span>
</button>
</div>
<div class="modal-body">
<label for="FirstName">Enter FirstName:</label>
<input type="text" id="FirstName" name="FirstName"><br><br>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-primary" #onclick="() => Done()">Done</button>
<button type="button" class="btn btn-secondary" data-dismiss="modal" #onclick="() => Close()">Close</button>
</div>
</div>
</div>
</div>
#if (ShowBackdrop)
{
<div class="modal-backdrop fade show"></div>
}
#code {
public Guid Guid = Guid.NewGuid();
public string ModalDisplay = "none;";
public string ModalClass = "";
public bool ShowBackdrop = false;
public void Open()
{
ModalDisplay = "block;";
ModalClass = "Show";
ShowBackdrop = true;
StateHasChanged();
}
public void Close()
{
ModalDisplay = "none";
ModalClass = "";
ShowBackdrop = false;
StateHasChanged();
}
public void Done()
{
}
}
I am using the component like this. I want to be able to use the FirstName entered in the Modal
in this component below to do other things when the user clicks on Done button.
#page "/modaltest"
<MappingPoc.UI.Pages.MyModal #ref="Modal"></MappingPoc.UI.Pages.MyModal>
<button #onclick="() => Modal.Open()">Open Modal</button>
#code {
private MappingPoc.UI.Pages.MyModal Modal { get; set; }
}
How can I pass the value of the first name entered in the modal to this component?
You can use an EventCallback to achieve this.
In your Modal component, bind the first name input textbox to a field using the #bind attribute:
<div class="modal-body">
<label for="FirstName">Enter FirstName:</label>
<input #bind="firstName" type="text" id="FirstName" name="FirstName"><br><br>
</div>
In the #code section of the same component, define a field called firstName of type string:
private string firstName;
This firstName will be bound to the textbox and any value update in the textbox will also set the value of firstName to the same value.
Secondly, in the same code section, add a public property of type EventCallback<string> as a parameter to the Modal component(by decorating it with the [Parameter] attribute:
[Parameter] public EventCallback<string> OnDoneCallback { get; set; }
In this code section, invoke the EventCallback using its InvokeAsync() method in the Done() method:
public void Done()
{
// ...
await InvokeAsync(() => OnDoneCallback.InvokeAsync(firstName));
}
This is all you need in your child Modal component.
In the #code section of the parent component, add a field to hold the first name from the child component:
private string _firstName;
Now, include a callback method to be executed in response to when the Done method of the child component calls the EventCallback:
private void OnModalDone(string firstName)
{
_firstName = firstName;
}
Now in your parent component where you are including the Modal component, pass the OnModalDone method as parameter for the EventCallback:
<MappingPoc.UI.Pages.MyModal #ref="Modal" OnDoneCallback="OnModalDone"></MappingPoc.UI.Pages.MyModal>
<h1>#_firstName</h1>
I have also added an <h1> tag to display the first name. Now, when you click the Done button of the modal, it will call the Done method. This Done method will call OnModalDone of the parent, with the parameter set as the value entered in the textbox of the modal. The entered value is available in OnModalDone as the parameter firstName.
You don't need to call StateHasChanged for event callbacks, it is called automatically.
See this working in Blazor fiddle: https://blazorfiddle.com/s/rbsg30wv
There are a few different ways you can do this depending on your needs:
Cascading Parameters: A good video for the basics of it - https://www.youtube.com/watch?v=ZmDMKp1Q8kA
An AppState approach: A good video for it covering two different ways: https://www.youtube.com/watch?v=BB4lK2kfKf0
Or you can pass it in as a parameter to the component: https://learn.microsoft.com/en-us/aspnet/core/blazor/components/data-binding?view=aspnetcore-5.0 (scroll down to the 'Binding with component parameters' section
I included videos as I believe they are more helpful but let me know if it is not clear.
I'm building my page and I was wondering if I can make my life easier and put some simple custom Input boxes inside method and them pass reference to my values to them
<div class="col-12 row">
<label class="col-2">#Caption</label>
<InputNumber class="form-control col-3" #bind-Value="#Value"/>
</div>
<br />
#code {
[Parameter]
public string Caption { get; set; }
[Parameter]
public int Value { get; set; }
}
And then use it like
<CustomInputNumber Caption="Price" Value="#Product.Price" />
It is possible to set value like that? Or pass object as reference? Thanks for help!
The way I would go about this is inheriting from inputbase and basically building your own input. Chrissainty has an excellent blog post about, which I think is much clearer then me citing half of what he already explains in that post.
https://chrissainty.com/building-custom-input-components-for-blazor-using-inputbase/
If however you really want to wrap the already existing inputcomponent, you could do it like this:
The component
<div class="col-12 row">
<label class="col-2">#Caption</label>
<InputNumber class="form-control col-3" Value="Value" TValue="int" ValueChanged="HandleValueChanged" ValueExpression="(()=>Value)" />
</div>
<br />
#code{
[Parameter]
public string Caption { get; set; }
[Parameter]
public int Value { get; set; }
[Parameter]
public EventCallback<int> ValueChanged { get; set; }
public void HandleValueChanged(int newValue)
{
ValueChanged.InvokeAsync(newValue);
}
}
Usage like:
<ExampleComponent #bind-Value="ExampleValue"/>
Here you basically override the existing events that exist on a default inputfield. When the default component notices a change, you call your own valuechanged event to pass the event to it's parent.
Though again, I think the first solution is much cleaner.
I am really frustrated with this, Because I look so many questions and didn't find my answer. I want to implement a global search in my ASP Net Core project. But I want to implement a search box in Layout page, so that It could be used in every page.
My ASP Net Core project shows directors and movies' information. I built an independent razor page for showing the result of search. I used a property named SearchTerm to get the search term as a query string and then search it in database tables:
//from Search.cshtml.cs
[BindProperty(SupportsGet = true)]
public string SearchTerm { get; set; }
And I implemented a search box in Layout view:
#model MastersOfCinema.Pages.SearchModel
// (I referenced to "SearchModel" and with this I'm only able to open that page. Other pages give the error below.)
<header>
<form method="get" class="SearchBox">
<div class="form-group">
<div class="input-group">
<input type="search" class="form-control" asp-for="SearchTerm" asp-route-searchTerm="SearchTerm" />
<span class="input-group-btn">
<button class="btn btn-default" asp-page="./../Search">
<i class="glyphicon glyphicon-search bg-light"></i>
</button>
</span>
</div>
</div>
</form>
</header>
With the code above, I get this error when I open any page (except the search page which is referenced in layout):
InvalidOperationException: The model item passed into the ViewDataDictionary is of type 'MastersOfCinema.Pages.Directors.IndexModel', but this ViewDataDictionary instance requires a model item of type 'MastersOfCinema.Pages.SearchModel'.
The error is related to #model MastersOfCinema.Pages.SearchModel apparently. I don't know what to replace it. If I remove that, I get error from asp-for="SearchTerm". Because it doesn't recognise SearchTerm property.
Error CS1963 An expression tree may not contain a dynamic
operation MastersOfCinema
I tried many approaches to implement a global search, including partial view and view components. But this is my first project and I am inexperienced. So please help me. Show me an easy approach.
Thanks for any help and if you need more information just ask.
The search works but as I said I get error when I open other pages. I can only open the seach page.
In case you need to see the whole code of Search.cshtml.cs:
public class SearchModel : PageModel
{
private readonly Context _context;
public SearchModel(Context context)
{
this._context = context;
}
public IEnumerable<Director> Director { get; set; }
public IEnumerable<Movie> Movie { get; set; }
[BindProperty(SupportsGet = true)]
public string SearchTerm { get; set; }
public async Task OnGetAsync(string searchTerm)
{
Director = await _context.Director.ToListAsync();
Movie = await _context.Movie.ToListAsync();
Director = GetDirectorByName(SearchTerm);
Movie = GetMovie(SearchTerm);
SearchTerm = searchTerm;
}
public IEnumerable<Director> GetDirectorByName(string searchString)
{
var query = from r in _context.Director
where r.Name.Contains(searchString) || r.Bio.Contains(searchString) ||
r.Country.Contains(searchString) || string.IsNullOrEmpty(searchString)
orderby r.Name
select r;
if (query.Any()) { searchTermFound = true; }
return query;
}
public IEnumerable<Movie> GetMovie(string searchString)
{
var query = from r in _context.Movie
where r.Title.Contains(searchString) || string.IsNullOrEmpty(searchString)
orderby r.Title
select r;
if (query.Any()) { searchTermFound = true; }
return query;
}
public bool searchTermFound = false;
}
I want to implement a search box in Layout page, so that It could be used in every page
InvalidOperationException: The model item passed into the ViewDataDictionary is of type 'MastersOfCinema.Pages.Directors.IndexModel', but this ViewDataDictionary instance requires a model item of type 'MastersOfCinema.Pages.SearchModel'.
To fix the issue and achieve the requirement, you can try to remove #model MastersOfCinema.Pages.SearchModel from _Layout.cshtml, then manually set name and value attribute of input field, like below.
<form method="get" class="SearchBox">
<div class="form-group">
<div class="input-group">
#*<input type="search" class="form-control" asp-for="SearchTerm" asp-route-searchTerm="SearchTerm" />*#
<input type="search" class="form-control" name="SearchTerm" value="#ViewData["SearchTerm"]" asp-route-searchTerm="SearchTerm" />
<span class="input-group-btn">
<button class="btn btn-default" asp-page="./Search">
<i class="glyphicon glyphicon-search bg-light"></i>Search
</button>
</span>
</div>
</div>
</form>
In Search.cshtml.cs
public async Task OnGetAsync(string searchTerm)
{
//...
ViewData["SearchTerm"] = SearchTerm;
}
Test Result