Delete all content of input text, without #bind in Blazor - c#

I would like to know if it is possible to delete everything that the user has entered in the input text controls, using C# in Blazor, but without the controls being binded.
I have several text input but without #bind, so I cannot delete variables associated with #bind
Is it possible to do something like a "for" through the controls, and ask if it is of the input text type to delete its content?
Thanks!

So in a situation like this where you are using an extension, it's probably best to look at how the extension is implemented and work out a solution from there. If you examine the source code, you will see that the text box for the filter that you want to clear is bound to column.Filter.SearchValue:
<TextEdit Text="#column.Filter.SearchValue" TextChanged="#(async (newValue) => await OnFilterChanged(column, newValue))" />
From there, it's just a matter of being able to clear out that property in C#. Unfortunately, Blazorise does not support public access to its DisplayableColumns property -- you need that in order to manipulate SearchValue. One hacky workaround would be to subclass the DataGrid, since the property is protected:
public class MyDataGrid<TItem> : DataGrid<TItem>
{
public IEnumerable<DataGridColumn<TItem>> MyColumns => DisplayableColumns;
}
With that, it's trivial to implement a "Clear Filters" button that clears all the filters.
<input type="button" value="Clear Filters" #onclick="ClearFilters" />
Implemented as:
private void ClearFilters()
{
foreach (var column in dataGrid.MyColumns)
{
column.Filter.SearchValue = null;
}
}
You also need to capture the DataGrid in a field:
private MyDataGrid<Person> dataGrid;
For that field to be set, you need to add #ref="dataGrid" to your grid.
Now, all that said, it's pretty clear that having to create a subclass is suboptimal. I've taken the liberty of opening an issue in their GitHub repo and referenced this answer as a workaround.
The full example sans the subclass is:
<MyDataGrid TItem="Person" Data="#persons" Filterable="true" #ref="dataGrid">
<DataGridColumn TItem="Person" Field="#nameof(Person.Name)" />
</MyDataGrid>
<input type="button" value="Clear Filters" #onclick="ClearFilters" />
#code {
private MyDataGrid<Person> dataGrid;
private void ClearFilters()
{
foreach (var column in dataGrid.MyColumns)
{
column.Filter.SearchValue = null;
}
}
private Person[] persons = new[]
{
new Person { Name = "John Doe" },
new Person { Name = "Jane Down" }
};
public class Person
{
public string Name { get; set; }
}
}
Note that due to an apparent limitation in Blazor, you will need to declare MyDataGrid in a separate file, and not as a class defined in a #code block.

Related

Blazo WASM - Separate into multiple components (MudBlazor)

I have an edit form, with multiple MudTabPanels inside.
Problem is, I have LOTS of properties for this class, and we've decided to split into multiple panels, that each contain an edit form with different forms/inputs.
Format is somewhat like this (pseudo-razor-code) :
<MudTabs>
<MudTabPanel Text="Section 1">
<EditForm>
<MudItem>
<EditField Property1>
<EditField Property2>
...
<EditField Property 10>
</EditForm>
</MudTabPanel>
<MudTabPanel Text="Section 2">
<EditForm>
<MudItem>
<EditField Propertyn11>
<EditField Propertyn12>
...
<EditField Property 20>
</EditForm>
</MudTabPanel>
..... lots of other panels here
<MudTabPanel Text="Section N">
<EditForm>
<MudItem>
<EditField Property98>
<EditField Property99>
...
<EditField Property100>
</EditForm>
</MudTabPanel>
</MudTabs>
Problem is :
I have +1000 lines of code just in this razor page!
VS 2022 Preview is struggling to give me a decent performance (on the UI seems to be working fine)but modifying just a property is a pain in the ass in VS.
I was thinking about moving each Panel into a separate component , and transmitting my entity as a Parameter.
But:
1).Right now, because I use all these into a single page razor, on the code page, let's say I have the method DoSomething(), I can use this method on each panel.
Will I need to repeat the DoSomething() on each component, if i'll split them ? Is there a way I can share that method?
2).Do you think this will impact the performance on the UI?
3).Is there any better way of doing this ?
LE: Updated my data binding example
Code behind:
private Article _article;
Example of some bindings in my first tab:
<MudNumericField T="int?" #bind-value="_article.ArticleID">
<MudSelect #bind-Value="_article.UnitPriceIntervals" OffsetY="true" Label="Unit Price Interval" Variant="Variant.Outlined" Margin="Margin.Dense" Dense="true">
#foreach (UnitPriceIntervals? item in (UnitPriceIntervals[])Enum.GetValues(typeof(UnitPriceIntervals)))
{
<MudSelectItem Value="item">#item</MudSelectItem>
}
</MudSelect>
Now, my article properties can also contain references to other data types that are stored in a different SQL table, with possibility to change them, based on a search.
Example :
_article.GeneralText1 = 1234
Why not use a
#foreach (FieldAttributes fsa in cAtribs)
{
<MudTabPanel Text=#fsa.key>
<EditForm>
<MudItem>
#for(int i=0;i<fsa.Value.Count();i++)
{
<EditField #fsa.Value.ElementAt(i) />
}
</MudItem>
</EditForm>
</MudTabPanel>
}
loop, where Dictionary<string, List<string>> cAtribs.
The key of the dictionary is "Section 1", ... "Section N" and the collection has the property names for each section.
you can build cAtribs ahead of time, or dynamically, or even using reflection.
This is more a comment than an answer, but there's not enough spacing to fit it into a comment.
Track which Tab you're in and only load the edit form for the specific tab. That will significantly reduce what needs to be rendered at one time. Something like:
#if (tabNo = 2)
{
// Edit Form 2
}
You don't show your data binding, but make sure you use a view data service to hold your model data.
Consider using a component for each edit form within a Tab?
There are many complications with multi-tab editors/wizards. How are you validating and when? Is you backend one model/data table?
If you want more detail, add a comment and I'll try and put together some demo code later today.
===== Update
First get your data out of your edit component and into a ViewService. Here's a wire framework for one.
using System.Threading.Tasks;
namespace StackOverflow.Answers
{
public class ArticleViewService
{
//set up your data access
// load as Scoped Service - one per user session
public Article Article { get; private set; }
public Task GetArticle()
{
// Your get article code here
return Task.CompletedTask;
}
public Task SaveArticle()
{
// Your save article code here
return Task.CompletedTask;
}
}
}
Next your section edit components. Your data comes from the inject view service and gets updated directly into the same service. No passing data between componnts.
<h3>Section1</h3>
<EditForm EditContext="_editContext">
<InputText #bind-Value="ViewService.Article.Name"></InputText>
// or your mud editor components
.....
</EditForm>
#code {
[Inject] private ArticleViewService ViewService { get; set; }
private EditContext _editContext;
protected override Task OnInitializedAsync()
{
_editContext = new EditContext(ViewService.Article);
return base.OnInitializedAsync();
}
}
Then you Article editor, with MudTabs. This should track the active tab and display only the correct section component. I haven't tested this but it "should" work (I don't use MudBlazor and don't have it installed.)
<MudTabs #bind-ActivePanelIndex="this.panelId">
<MudTabPanel Text="Item One" ID='"pn_one"'>
#if(this.PanelId = 1)
{
\\ Section 1 componwnt
}
</MudTabPanel>
<MudTabPanel Text="Item Two" ID='"pn_two"'>
#if (this.PanelId = 2)
{
\\ Section 2 componwnt
}
</MudTabPanel>
<MudTabPanel Text="Item Three" ID='"pn_three"'>
#if (this.PanelId = 2)
{
\\ Section 3 componwnt
}
</MudTabPanel>
</MudTabs>
#code {
private int PanelId {
get => _panelId;
set => {
if (value != _panelId )
{
_panelId = value;
StateHasChanged();
}
}
}
private int _panelId = 1;
}

Keeping track of 40+ control values

I am in need of some guidance for the following design.
I have a tab control that contains various group boxes. Within the group box, there are specific controls that relates to that group box. For example:
Now whenever a change is made to any control in the group box, the value for the control needs to be tracked because at the end of the application run cycle, the control data will need to be saved to a file that contains that value. An example file is:
HOT_STANDBY_FEATURE_ENABLE [val from control here]
HEART_BEAT_DIGITAL_OUTPUT [val from control here]
....
A design that I have in mind has another that has just properties that the group box form sets whenever a ValueChanged event occurs on a control.
Example code:
class ConfigurationValues
{
public int HotStandbyValue { get; set; }
public int HeartBeatDigitalOutputValue { get; set; }
//...add all other controls here
}
The downside that I see to this is that there are 40 controls on that tab page, so I'd have to manually type each property. When the file needs to be generated at the end of the application run cycle, I have a method that gets the value of the control need.
Example:
private void GenerateFile()
{
string[] file =
"HOT_STANDBY_FEATURE_ENABLE " + ConfigurationTabSettings.HotStandbyValue;
}
Another design consideration I need to make is that whenever a user clicks "Open Configuration File", the values from the file from disk need to be loaded into the properties so the form can take that data on startup and populate the controls within the group boxes with their respective values.
Any suggestions on this design would be greatly appreciated. I know this is not the most efficent way to do this and am not the most experienced programmer, so any Google keywords I can search for would be great also.
You could xml serialise and xml deserialise your ConfigurationValues class rather than writing manual "generate file" and "read file" methods
http://support.microsoft.com/kb/815813
You'll need to bind the controls Text or Value properties to the properties in your ConfigurationValues class e.g.
ConfigurationValues cv = Repository.ReadConfigValues();
numPulseFilterDelay.DataBindings.Add("Value", cv, "PulseFilterDelay");
// Add the rest of your control bindings here
on the btnSave_Click() of your Form, end the current edit on the form and save the config values
void btnSave_Click(object sender, EventArgs e)
{
BindingContext[cv].EndCurrentEdit(); // Commits all values to the underlying data source
Repository.SaveConfigValues(cv);
}
In your repository class you'll need methods to Load() and Save() the data. You can put XmlSerialization code in here, or write your own format (depending on your requirements)
public class Repository
{
public static ConfigurationValues LoadConfigValues()
{
var cv = new ConfigurationValues();
string[] lines = File.ReadAllLines("values.cfg");
foreach (string cfg in lines)
{
string[] nameValue = cfg.Split(new char[] { ' ' } ); // To get label/value
if (nameValue[0] == "HOT_STANDBY_FEATURE_ENABLE")
cv.HotStandbyFeatureEnable = nameValue[1];
else if (nameValue[0] == "SOME_OTHER_PROPERTY")
cv.SomeOtherProperty = nameValue[2];
// Continue for all properties
}
return cv;
}
public static void SaveConfigValues(ConfigurationValues cv)
{
var builder = new StringBuilder();
builder.AppendFormat("HOST_STANDBY_FEATURE_ENABLE {0}\r\n", cv.HostStandbyFeatureEnable);
// Add the rest of your properties
File.WriteAllText("values.cfg", builder.ToString());
}
}

Show only a part of an ItemsControl's source at first

I have an ItemsControl displaying a collection of files. Those files are sorted by most recent modification, and there's a lot of them.
So, I want to initially only show a small part (say, only 20 or so) of them, and display a button labelled "Show More" that would reveal everything when clicked.
I already have a solution, but it involves using a good old LINQ Take on my view model's source property. I was wondering if there was a cleaner way.
Thanks.
Why not have the object that you assign to the ItemsSource handle this logic - on first assignment, it would report a limited subset of the items. When Show More is clicked, the object is updated to show more (or all entries) and then notifies the framework that the property has changed (e.g. using the IPropertyNotifyChanged).
public class MyItemSource
{
private List<string> source = { ... };
public MyItemSource()
{
this.ShowThisMany = 20;
}
public int ShowThisMany
{
get;
set; // this should call\use the INotifyPropertyChanged interface
}
public IEnumerable<string> this[]
{
return this.source.Take(this.ShowThisMany);
}
}
...
MyItemsSource myItemsSource = new MyItemsSource();
ItemsControl.Source = myItemsSource;
...
void OnShowMoreClicked(...)
{
myItemsSource.ShowThisMany = 50;
}
In order to do this, you need to create some sort of 'view' on your data. There is nothing within the WPF framwork that will give you this functionality for free. In my opinion, a simple bit of Linq, Take(), is a clean and simple solution.

Moving data between two user controls in WinForm Application

As a course project i'm building a form in c# which contains two user controls.
The first user control has a checkedlistbox and the second control has also a checkedlistbox when the first control checkedlistbox will contain list of people (male/female) and the second user control the checkedlistbox will have two options: male, female and when I click a button on the first control which says: "update friends" it's suppose to go to the second control and check if we selected male or female and according to that to update the checkedlistbox in the first user control with friends by gender type by what was selected on the second control.
Basically I want to raise an event every time the button on the first control selected then to get the data from the second control to the first control.
Is it possible to do so between two controls who are inside a form and are different controls?
Any help will be appriciated.
Thanks.
To do this "correctly," you would want to use something like the MVC architecture. It's definitely a lot more work initially to understand and implement but is very useful to know if you plan on doing any serious UI application development. Even if you don't go all the way with it, the concepts are useful to help design even "quick and dirty" applications.
Define your data model without thinking in terms of the UI, e.g.:
internal enum Gender
{
Male,
Female
}
internal class Person
{
public Gender Gender { get; set; }
public string Name { get; set; }
}
// . . .
// Populate the list of people
List<Person> allPeople = new List<Person>();
allPeople.Add(new Person() { Gender = Gender.Male, Name = "Xxx Yyy" });
allPeople.Add(new Person() { Gender = Gender.Female, Name = "Www Zzz" });
// . . .
For the view portion, you would typically use data binding on the UI controls so that the controls will automically reflect changes to the underlying data. However, this can get difficult especially if you are not using a database-like model (e.g. System.Data.DataSet). You may opt to "manually" update the data in the controls which might be fine in a small app.
The controller is the portion that uses the UI events and makes changes to the model, which may then be reflected as changes in the view.
internal class Controller
{
private Gender selectedGender;
private List<Person> allPeople;
private List<Person> friends;
public Controller(IEnumerable<Person> allPeople)
{
this.allPeople = new List<Person>(allPeople);
this.friends = new List<Person>();
}
public void BindData(/* control here */)
{
// Code would go here to set up the data binding between
// the friends list and the list box control
}
// Event subscriber for CheckedListBox.SelectedIndexChanged
public void OnGenderSelected(object sender, EventArgs e)
{
CheckedListBox listBox = (CheckedListBox)sender;
this.selectedGender = /* get selected gender from list box here */;
}
// Event subscriber for Button.Click
public void OnUpdateFriends(object sender, EventArgs e)
{
this.friends.AddRange(
from p in this.allPeople
where p.Gender == this.selectedGender
select p);
// If you use data binding, you would need to ensure a
// data update event is raised to inform the control
// that it needs to update its view.
}
}
// . . .
// On initialization, you'll need to set up the event handlers, etc.
updateFriendsButton.Click += controller.OnUpdateFriends;
genderCheckedListBox.SelectedIndexChanged += controller.OnGenderSelected;
controller.BindData(friendsListBox);
// . . .
Basically, I recommend not having controls talk directly, but rather through a controller-like class as above which has knowledge of the data model and the other controls in the view.
Of course it's possible: you need to make the link between the 2 controls in the form.
Just declare an event 'ButtonClicked' in control #1
Then make a public method 'PerformsClick' on the control #2
And in the form, in the constructor, after the call to InitializeComponent, link the event from the control #1 to the method to the control #2:
control1.ButtonClicked += delegate(sender, e) {
control2.PerformsClick();
};
(I type on the fly to give you an idea, it'll surely not compile)
If you want to pass any data, just add parameters in the PerformsClick method.

Strange behaviour in ASP.NET MVC: removing item from a list in a nested structure always removes the last item

Scenario
I have a parent/child model (to be exact a small questionnaire form and a one or more number of contacts). For historic reasons, all of this would have been done on the same form so user would have a form for the parent and one child and they would hit a button to add more children. Child has a few standard fields and the same with the parent, nothing fancy. Main requirement is that the data must not touch the database until all is valid and setup while I would have to go back to server for adding deleting children.
Implementation
It was very quick to get this working in ASP.NET MVC (using MVC 2 with VS 2010). I got two models, one for parent and one for the child and got only one controller. Controller has a Create Method which is a get and gets a default view with a fresh brand new parent containing one child. I use editor template for the child model which works nicely.
I have one HTML form which has a "save" and "add child" and I have "delete" button for each form. Since this cannot be stored in database, I store the temp model in the form itself and it goes back and forth between browser and server. Perfromance is not much of an issue here but the cost of development since there are quite a few of these forms - so please do not get distracted too much by suggesting an alternative approach although I appreciate comments anyway.
In order to find out which child to delete, I create temp GUID Ids and associate them with the child. This will go onto the HTML input's value for delete button (usual trick when you have multiple actions and the same form).
I have disabled caching.
Issue
Please have a look at the snippets below. I have debugged the code and I have seen always correct GUID being passed, correct item removed from the list in the controller and correct items being rendered in the template. BUT ALWAYS THE LAST ONE GETS DELETED!! I usually click the first delete and can see that the last gets deleted. I carry on and first item is the last being deleted.
Controller
public ActionResult Create()
{
EntryForm1 entryForm1 = new EntryForm1();
entryForm1.Children.Add(new Child("FILL ME", "FILL ME"){ TempId = Guid.NewGuid()});
return View("EntryForm1View", entryForm1);
}
[HttpPost]
public ActionResult Create(EntryForm1 form1, FormCollection collection, string add)
{
if (add == "add")
form1.Children.Add(new Child("FILL ME", "FILL ME") {TempId = Guid.NewGuid()});
var deletes = collection.AllKeys.Where(s => s.StartsWith("delete_"));
collection.Clear();
if (deletes.Count() > 0)
{
string delete = deletes.FirstOrDefault();
delete = delete.Replace("delete_", "");
Guid g = Guid.Parse(delete);
var Children = form1.Children.Where(x => x.TempId == g).ToArray();
foreach (Child child in Children)
{
form1.Children.Remove(child);
}
// HERE CORRECT ITEM IS DELETED, BELIEVE ME!!
}
if (ModelState.IsValid)
{
return Redirect("/");
}
return View("EntryForm1View", form1);
}
View snippet
<% for (int i = 0; i < Model.Children.Count;i++ )
{%>
<h4> <%: Html.EditorFor(m=>m.Children[i])%></h4>
<%
}%>
<p>
<input type="submit" value="Create" name="add" />
<input type="submit" value="add" name="add" />
</p>
Child Editor template snippet
<%: Html.HiddenFor(x=>x.TempId) %>
</span>
<input type="submit" name='delete_<%: Html.DisplayTextFor(m => m.TempId) %>' value="Delete" />
Many thanks for your time and attention
UPDATE
I was asked for model classes and I am sharing them as exactly as they are.
Entryform1 is the parent and Somesing is the child.
public class Somesing
{
public Somesing()
{
}
public Somesing(string o, string a) : this()
{
OneSing = o;
AnozerSing = a;
}
[StringLength(2)]
public string OneSing { get; set; }
[StringLength(2)]
public string AnozerSing { get; set; }
public Guid TempId { get; set; }
}
public class EntryForm1
{
public EntryForm1()
{
Sings = new List<Somesing>();
}
public string FirstName { get; set; }
public string LastName { get; set; }
public int Age { get; set; }
public List<Somesing> Sings { get; set; }
}
I believe that problem lies with ModelState. When the view gets rendered, which I assume is where the issue lies, after the POST, the last value is not displayed i.e. removed from the view.
The issue is that Model.Children.Count will return the correct number of elements to display.
Lets break this down...
So if you have initially had 5 then removed the first one which is at index 0 based on the Guid, you now have items 4 items left with indexes 1 to 4.
However, when rendering the view after the post, the HtmlHelpers do not look at the values in model posted, but rather the values contained within the ModelState. So in the ModelState, item with index 0 still exists and since the loop is now looping to 4, the last element will not be displayed.
The solution, use ModelState.Clear()
OK, as Ahmad pointed out, ModelState is the key to the issue. It contains the collection as such:
FirstName
LastName
...
Sings[0].OneSing
Sings[0].AnozerSing
Sings[1].OneSing
Sings[1].AnozerSing
Sings[2].OneSing
Sings[2].AnozerSing
Now if I delete item 0 from the list, now the items will move up in the list and the data in the ModelState will go out of sync with the model. I had expected ASP.NET MVC to be clever enough to find out and re-order, but well that is asking for too much.
I actually implemented PRG (post-redirect-get) and by keeping the model in session, I was able to display correct information but again, this will remove all the validation in the collection and if model itself is valid, it will happily save and redirect back to home "/". Clearly this is not acceptable.
So one solution is to remove all items in the ModelState and then add a new entry for the model itself (with key of EmptyString). This can actually work alright if you populate it with error "Item deleted" as this will be displayed in the validation summary.
Another solution is to manually change the items in the model state and re-arrange them based on the new indexes. This is not easy but possible.
ModelState.Clear() will Solved this problem.
ModelState.Clear() is used to clear errors but it is also used to force the MVC engine to rebuild the model to be passed to your View.

Categories

Resources