Hi i try to create a search function, showing my data via blazor components.
The search function works fine, and return the result i want. But it does not update the old component. So if my search return 1 result, then the component only update the title, but other data from my model, will stay from the first loaded card. If search for an extra letter so item not exist, all the component will disapeer. But when i delete the last again, the component will show up again, with the right data.
<input type="search" #bind-value="SearchValue" #bind-value:event="oninput">
<div class="row justify-content-center mb-5">
#foreach (var item in FoundItems)
{
<ItemCard ItemDTO=item />
}
</div>
#code
public string SearchValue { get; set; } = "";
private List<ItemDTO> Items { get; set; }
protected async override Task OnInitializedAsync()
{Items = await _http.GetFromJsonAsync<List<ItemDTO>>("api/item");}
{
List<ItemDTO> FoundItems => Items.Where(i =>
i.Description.ToLower().Contains(SearchValue.ToLower())).ToList();
}
I solved it allready.
In my component i added.
protected override void OnParametersSet()
{
StateHasChanged();
}
Related
I'm currently writing a project using Blazor Server (.net 7.0).
So If I have a model with multiple datetime? properties on it, I wish to create a button next to quite a number of datetime field to insert the current date and time which calls a method and updates the property with the current date and time.
Rather than creating a method for every button eg
<button #Onclick="#UpdateNowOn_DateReceived"></Button>
<button #Onclick="#UpdateNowOn_InitialContact"></Button>
protected void UpdateNowOn_DateReceived()
{
Model.Details.DateReceived = DateTime.Now;
StateHasChanged();
}
protected void UpdateNowOn_InitialContact()
{
Model.Details.InitialContact = DateTime.Now;
StateHasChanged();
}
I was hoping I could write a method that I could simply pass the property into an update its value. I'm very new to Blazor and C# so this is all quite new to me (learning as I go).
So I was hoping its possible to do it more like this
<button #Onclick="#UpdateNowOn(Model.Details.DateReceived)"></Button>
<button #Onclick="#UpdateNowOn(Model.Details.InitialContact)"></Button>
protected void UpdateNowOn(DateTime property?) <-- what to pass here
{
Property = DateTime.Now;
StateHasChanged();
}
Any help is appreciated
I've tried to read and use the following but I'm not sure its what I'm after:
Pass property itself to function as parameter in C#
You don't need to pass a property into anything. You are thinking button not input control.
Create a component that implements the standard Blazor bind framework.
Here's an example that I think fits your requirements.
<button disabled="#_isSet" #attributes=this.AdditionalAttributes #onclick=this.OnChange>#this.Text</button>
#code {
[Parameter] public DateTime? Value { get; set; }
[Parameter] public EventCallback<DateTime?> ValueChanged { get; set; }
[Parameter(CaptureUnmatchedValues = true)] public IReadOnlyDictionary<string, object>? AdditionalAttributes { get; set; }
[Parameter, EditorRequired] public string? Text { get; set; } = "Not Set";
[Parameter] public bool DisableIfSet { get; set; } = false;
private bool _isSet => this.DisableIfSet && this.Value is not null;
private async Task OnChange()
=> await this.ValueChanged.InvokeAsync(DateTime.Now);
}
And then in use:
#page "/"
<PageTitle>Index</PageTitle>
<ButtonComponent class="btn btn-primary" Text="Log Received" #bind-Value=_selectedValue1 DisableIfSet=true />
<div class="alert alert-info m-3 p-2">#(_selectedValue1.ToString() ?? "Not Set")</div>
<ButtonComponent class="btn btn-success" Text="Set Initial Contact Date" #bind-Value=_selectedValue2 />
<div class="alert alert-info m-3 p-2">#(_selectedValue2.ToString() ?? "Not Set")</div>
#code {
private DateTime? _selectedValue1;
private DateTime? _selectedValue2;
}
Thank you all for your responses. I was seriously over thinking it and placing an inline function worked in this instance as suggested by thewallrus
I simply added an onclick event to the button as follows
<button #onclick="() => {#Model.Details.DateReceived = DateTime.Now;}
I am attempting to create a QuickGrid filter with Blazor
<div class="grid">
<QuickGrid Items="#itemsQueryable" Pagination="#pagination">
<PropertyColumn Property="#(c => c.name)" Sortable="true" Class="brewery_name" />
<PropertyColumn Property="#(c => c.city)" Sortable="true" Align="Align.Right" />
<PropertyColumn Property="#(c => c.state)" Sortable="true" Align="Align.Right" >
<ColumnOptions>
<div class="search-box">
<input type="search" autofocus #bind="stateFilter" #bind:event="oninput" placeholder="State ..." />
</div>
</ColumnOptions>
</PropertyColumn>
<PropertyColumn Property="#(c => c.brewery_type)" Sortable="true" Align="Align.Right" />
#*<ColumnOptions>
<div class="search-box">
<input type="search" autofocus #bind="typeFilter" #bind:event="oninput" placeholder="Brewery Type ..." />
</div>
</ColumnOptions>*#
<PropertyColumn Property="#(c => c.website_url)" Sortable="true" Align="Align.Right" />
</QuickGrid>
</div>
Above is the code to display to the screen.
#code{
PaginationState pagination = new PaginationState { ItemsPerPage = 10 };
IQueryable<BreweryEntry>? itemsQueryable;
string? stateFilter;
string? typeFilter;
IQueryable<BreweryEntry> FilteredBreweries
{
get
{
var result = itemsQueryable?.Where(c => c.state != null);
if (!string.IsNullOrEmpty(stateFilter))
{
result = result.Where(c => c.state.Contains(stateFilter, StringComparison.CurrentCultureIgnoreCase));
}
//if (!string.IsNullOrEmpty(typeFilter))
//{
// result = result.Where(c => c.brewery_type.Contains(typeFilter, StringComparison.CurrentCultureIgnoreCase));
//}
return result;
}
}
protected override async Task OnInitializedAsync()
{
try
{
itemsQueryable = (await Http.GetFromJsonAsync<BreweryEntry[]>("https://api.openbrewerydb.org/breweries?per_page=50")).AsQueryable();
pagination.TotalItemCountChanged += (sender, eventArgs) => StateHasChanged();
}
catch (Exception e)
{
Console.WriteLine(e.Message);
throw e;
}
}
private async Task GoToPageAsync(int pageIndex)
{
await pagination.SetCurrentPageIndexAsync(pageIndex);
}
private string? PageButtonClass(int pageIndex)
=> pagination.CurrentPageIndex == pageIndex ? "current" : null;
private string? AriaCurrentValue(int pageIndex)
=> pagination.CurrentPageIndex == pageIndex ? "page" : null;
public void NavTo()
{
NavigationManager.NavigateTo("/random");
}
public class BreweryEntry
{
public string? name { get; set; }
public string? city { get; set; }
public string? state { get; set; }
public string? brewery_type { get; set; }
public string? website_url { get; set; }
}
}
I've then copied the #code section above. The issue I'm having is that while the search box is appearing, my data is not being filtered at all. It should filter as the user is typing but even when I press search, my data doesn't filter. I can't seem to work out the issue and any help would be appreciated.
I don't see when FilteredBreweries is accessed through the components. It seems you are only accessing itemsQueryable by this line:
<QuickGrid Items="#itemsQueryable" Pagination="#pagination">
The itemsQueryable is being instantiated once, but is never modified based on the code you submitted:
//the items are retreived
itemsQueryable = (await Http.GetFromJsonAsync<BreweryEntry[]>("https://api.openbrewerydb.org/breweries?per_page=50")).AsQueryable();
//this doesn't modify the itemsQueryable, but just gets the items where the state is not null
var result = itemsQueryable?.Where(c => c.state != null);
Therefore, I don't see why QuickGrid should be changed if it always shows the items in itemsQueryable, which do not change.
What I think you meant to do is access FilteredBreweries, so your QuickGrid component should look like this:
<QuickGrid Items="#FilteredBreweries" Pagination="#pagination">
I haven't been able to test it since the code you provided was partial. I don't have access to the code of all of your components or class implementations, so let me know if I helped you.
I have a simple collection of "Conversation" like this:
public class Conversation
{
public string? contactName { get; set; }
public string? thread { get; set; }
public DateTime newestMsg { get; set; }
}
I was originally using #foreach to put these onto a page to display as a scrollable list. I'd load 30 of them from an API call and populate them into "ConversationList":
#foreach (var conv in ConversationList)
{
<div class="conversation">
<div>#conv.contactName</div>
<div>#conv.thread</div>
<div>#conv.newestMsg</div>
</div>
}
#code {
Conversation[]? ConversationList = new Conversation[] { };
}
I saw Blazor has the Virtualize component which would let me have tons of conversations be scrollable and load on demand, so I changed my code to use it:
<Virtualize ItemsProvider="#LoadConversations" Context="conv">
<ItemContent>
<div class="conversation">
<div>#conv.contactName</div>
<div>#conv.thread</div>
<div>#conv.newestMsg</div>
</div>
</ItemContent>
</Virtualize>
#code {
private async ValueTask<ItemsProviderResult<Conversation>> LoadConversations(ItemsProviderRequest request)
{
string apiURL = "https://myAPI:00000?start=" + request.StartIndex + "&limit=" + request.Count;
ConversationRequest? convRequest = await Http.GetFromJsonAsync<ConversationReq>(apiURL);
if (convRequest != null && convRequest.success)
{
return new ItemsProviderResult<Conversation>(convRequest.Conversations, request.StartIndex + request.Count + 4);
}
else
{
return new ItemsProviderResult<Conversation>(null, 0);
}
}
}
In my first attempt, I could select a specific item in "ConversationList" and modify it's contents, having the state change on the page. For example, if a new message came in from SignalR, I could select a Conversation by it's "thread" property and update the "newestMsg" datetime.
Now that I have them Virtualized instead, it's not clear how to do this. I can't use Virtualize's "ItemsProvider" and "Items" at the same time. I want to keep the ItemsProvider for the sake of automatic loading while scrolling, knowing the StartIndex and Count automatically.
I just started to have a look in blazor (v0.3) and doing some test I wanted to add a list using blazor
First I created a List<string> to test a simple list in the same page
<ul>
#foreach (var item in listItems)
{
<li>#item</li>
}
</ul>
#functions {
private List<string> listItems = new List<string>();
private string newItem;
private void AddItem()
{
if (string.IsNullOrEmpty(newItem))
return;
listItems.Add(newItem);
newItem = "";
}
}
this is working fine, is adding every element to the list when I add it. but then, i tried to add components, add a single component was easy, based on this question here but for a list I had the next problem:
I created a <li> compontent just to test the functionality of components, here is the component view
<li id="#ID">
#Text
</li>
#functions {
[Parameter]
string Text { get; set; }
[Parameter]
string ID { get; set; }
}
then in the parent view
<input type="text" bind="TxtExample" name="inpAdd"/>
<button onclick="#addCompoment">add comp1</button>
<div class="simple-list-list">
#if (!componentListTest.Any())
{
<p>You have no items in your list</p>
}
else
{
<ul>
#foreach (var item in componentListTest)
{
#item
}
</ul>
}
</div>
#functions {
private List<RenderFragment> componentListTest { get; set; }
private int currentCount {get; set;}
private string TxtExample { get; set; }
protected override void OnInit()
{
currentCount = 0;
componentListTest = new List<RenderFragment>();
}
protected void addCompoment()
{
componentListTest.Add(CreateDynamicComponent(currentCount));
currentCount++;
}
RenderFragment CreateDynamicComponent(int counter) => builder =>
{
var seq = 0;
builder.OpenComponent(seq, typeof(listExample));
builder.AddAttribute(++seq, "Text", "text -- "+TxtExample);
builder.AddAttribute(++seq, "id","listed-"+counter);
builder.CloseComponent();
};
}
when I load the fist element is loaded correctly:
but when I entered the second one, all of them are replaced for the last one:
Any idea whats going on?
You are making it too complicated. You don't need to dynamically instantiate components for this scenario to work.
You can just do a:
<ul>
#foreach (var item in listItems)
{
<myComponent bind-myVar="#item"></myComponent>
}
</ul>
And the components will be instantiated for you.
Also see here how to make the parameters work on your component.
This is because TxtExample is global to the component. When Blazor detects a potential UI change, it recalculates the entire component and updates the DOM with any differences. So when you change the textbox, TxtExample is updated and then the Razor is recalculating, inserting the new value of TxtExample for all rows.
I am creating the survey application for which I am creating the controls dynamically(Like checkbox,radio-button,textbox etc).
Each question will have the controls depending upon the control type assigned to the question and on question type the answer choices(checkbox, radio button) will be rendered.
On Next/Previous navigation I am storing current page answers in the database. While navigating the page I am doing ajax call for database saving and my UI/controls are NOT in the form.
I have created ViewModel based on my LMS_SurveyQuestions and LMS_SurveyQuestionOptionChoice table.
So, while creating the UI in view in for loop, I have directly assigned SurveyQuestionOptionChoiceID as Control ID while creating the AnswerChoice controls and stored the same in the table SurveyUserAnswer table.
Model
public class LMS_TraineeSurveyPaginationViewModel
{
public List<LMS_SurveyQuestions> SurveyQuestions { get; set; }
public List<LMS_SurveyQuestionOptionChoice> SurveyQuestionOptionChoice { get; set; }
public SurveyPager Pager { get; set; }
}
and this is how I rendered the view
#foreach (var item in Model.SurveyQuestions)
{
foreach (var data in Model.SurveyQuestionOptionChoice.Where(x => x.SurveyQuestionID == item.SurveyQuestionID).ToList())
{
if (item.QuestionTypeID == QuestionType.RadioButton)
{
<li style="list-style:none;">
<input type="radio" name="rb" id="#data.SurveyQuestionOptionChoiceID" />
<span>#data.OptionChoice</span>
</li>
}
else if (item.QuestionTypeID == QuestionType.CheckBox)
{
<li style="list-style:none;">
<input type="checkbox" id="#data.SurveyQuestionOptionChoiceID" name="#data.SurveyQuestionOptionChoiceID" " />
<span>#data.OptionChoice</span>
</li>
}
}
}
and while saving the answer into database I have created the JSON/JS array as model for SurveyUserAnswer and saved it into database as follows. Below is example for radio button
function SaveValues() {
var surveyQuestion = #Html.Raw(Json.Serialize(Model.SurveyQuestions.ToArray()));
var surveyQuestionOptionChoide = #Html.Raw(Json.Serialize(Model.SurveyQuestionOptionChoice.ToArray()));
for (item in surveyQuestion) {
var surveyQuestionID=surveyQuestionViewModel[item].SurveyQuestionID;
var filteredData = surveyQuestionOptionChoide.filter(function(filteredItem) {
return (filteredItem.SurveyQuestionID==surveyQuestionID);
});
for (optionChoice in filteredData) {
if(surveyQuestion[item].QuestionTypeID=='#QuestionType.RadioButton') {
if (($('#'+SurveyQuestionOptionChoiceID).prop("checked"))) {
surveyUserAnswer.push({ SurveyUserAnswerID: filteredData[optionChoice].SurveyUserAnswerID==null?0:filteredData[optionChoice].SurveyUserAnswerID,
SurveyQuestionOptionChoiceID: SurveyQuestionOptionChoiceID,SurveyUserID:'#ViewBag.SurveyUserID',AnswerText:null,
MarksObtained:filteredData[optionChoice].Marks,WhenCreated:'#DateTime.UtcNow',WhoCreated:'#tenant.UserID'});
}
}
}
}
$.post('#Url.Action("GetTraineeSurvey", "Survey")', {SurveyID:surveyID,page:page, surveyUserAnswer: surveyUserAnswer,PrevBranchQuestionPage:currentPage,IsBranchQuestionAvailable:IsBranchQuestionAvailable }, function (data) {
$('#surveyModalContent').html('');
$('#surveyModalContent').html(data);
$("#surveyModal").modal('show');
}).fail(function() {
alert( "error in GetTraineeSurvey" );
}).success(function() { });
}
So, my question is how can I validate dynamically created controls in this scenario ?
You can use Unobtrusive jQuery validation basic on data attributes on controls.
Read more about that on this LINK.