Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

MudForm: Add ReadOnly and Disabled properties #6511

Merged
merged 13 commits into from
Apr 1, 2023

Conversation

Mr-Technician
Copy link
Member

Description

This PR adds ReadOnly and Disabled properties to the MudForm. They will automatically apply to all children forms and inputs to simplify working with large forms.

How Has This Been Tested?

I have added unit tests.

Types of changes

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to not work as expected)

Checklist:

  • The PR is submitted to the correct branch (dev).
  • My code follows the code style of this project.
  • I've added relevant tests.

@github-actions github-actions bot added enhancement New feature or request PR: needs review labels Mar 21, 2023

if (formControl is IReadOnlyDisabledFormComponent component) //automaticlaly set the readonly and disabled state
{
component.ReadOnly = ReadOnly || (ParentMudForm?.ReadOnly).GetValueOrDefault();
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could be refactored to ?? false


namespace MudBlazor.Interfaces
{
public interface IReadOnlyDisabledFormComponent : IFormComponent
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could be renamed?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have no better idea

foreach (var control in _formControls.Where(x => x is IReadOnlyDisabledFormComponent))
{
((IReadOnlyDisabledFormComponent)control).ReadOnly = _readOnly;
control.InternalStateHasChanged();
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This StateHasChanged call is necessary to ensure the controls accept their new state. Alternatively, the MudForm could be cascaded with Fixed="false" but in my testing this broke a number of other tests.

@henon henon self-requested a review March 21, 2023 18:24
@codecov
Copy link

codecov bot commented Mar 21, 2023

Codecov Report

Patch coverage: 98.18% and project coverage change: +0.01 🎉

Comparison is base (e0afebd) 91.37% compared to head (7112cde) 91.38%.

Additional details and impacted files
@@            Coverage Diff             @@
##              dev    #6511      +/-   ##
==========================================
+ Coverage   91.37%   91.38%   +0.01%     
==========================================
  Files         397      397              
  Lines       14919    14930      +11     
==========================================
+ Hits        13632    13644      +12     
+ Misses       1287     1286       -1     
Impacted Files Coverage Δ
src/MudBlazor/Base/MudFormComponent.cs 87.81% <0.00%> (-0.38%) ⬇️
...azor/Components/Autocomplete/MudAutocomplete.razor 96.00% <ø> (ø)
...rc/MudBlazor/Components/CheckBox/MudCheckBox.razor 100.00% <ø> (ø)
...zor/Components/DatePicker/MudDateRangePicker.razor 100.00% <ø> (ø)
src/MudBlazor/Components/Input/MudRangeInput.razor 85.71% <ø> (ø)
...azor/Components/NumericField/MudNumericField.razor 100.00% <ø> (ø)
src/MudBlazor/Components/Picker/MudPicker.razor 91.66% <ø> (ø)
src/MudBlazor/Components/Radio/MudRadioGroup.razor 100.00% <ø> (ø)
src/MudBlazor/Components/Select/MudSelect.razor 87.50% <ø> (ø)
src/MudBlazor/Components/Switch/MudSwitch.razor 100.00% <ø> (ø)
... and 19 more

Help us with your feedback. Take ten seconds to tell us how you rate us. Have a feature suggestion? Share it here.

☔ View full report in Codecov by Sentry.
📢 Do you have feedback about the report comment? Let us know in this issue.

@Mr-Technician
Copy link
Member Author

If someone sets ReadOnly manually, the form will override the setting which isn't desirable. I need to make some changes to avoid that.

@ScarletKuro
Copy link
Member

For the MudForm? You can implement them explicitly too i guess. If someone casts to interface he gets the access, but idk why anyone would do that.

@Mr-Technician
Copy link
Member Author

@ScarletKuro I meant that if someone set ReadOnly or Disabled on an individual form control, it would be completely overridden by the MudForm. The MudForm ReadOnly/Disabled defaults to false so all child components will ignore their own ReadOnly/Disabled parameters.

@henon
Copy link
Collaborator

henon commented Mar 22, 2023

Yes, I agree. Individual parameters should always override general parent parameters.

@ScarletKuro
Copy link
Member

ScarletKuro commented Mar 22, 2023

Ok, i got it now, but I'd say it depends.
Usually UI frameworks that I work with have following logic:
If you set IsDisabled / IsReadOnly = true on parent then all child components will get this state and it will be locked, i.e. if you set IsDisabled = false on child component it won't have effect since parent has IsDisabled = true.
But if parent has IsDisabled = false, and individual component has IsDisabled = true, then parent shouldn't override this.

@henon
Copy link
Collaborator

henon commented Mar 22, 2023

I guess that would be plausible behavior too

@dennisrahmen
Copy link
Contributor

dennisrahmen commented Mar 22, 2023

I set individual form controls to read only based on previously selected values.

Then when saving or updating data I would like to set the hole form to read only regardless of the state of individual components.

Maybe when setting read only on the form to 'null' it would use the individual form control settings and 'true' or 'false' would override them?

@Mr-Technician
Copy link
Member Author

@ScarletKuro I like this idea and it would be easy to implement using or: Your logic above boils down to if either the parent or child have Disabled/ReadOnly = true, then that property is true.

Alternatively as @dennisrahmen said the parent properties could be nullable and not be overridable when set.

@Mr-Technician
Copy link
Member Author

Additionally, as there can be nested forms, I think the override behavior should be reflected in how nested forms behave.

src/MudBlazor/Interfaces/IReadOnlyDisabledFormComponent.cs Outdated Show resolved Hide resolved
if (Disabled || (ParentMudForm?.Disabled).GetValueOrDefault() != _disabled) //only run if the disabled state has changed
{
_disabled = Disabled || (ParentMudForm?.Disabled).GetValueOrDefault();
foreach (var control in _formControls.Where(x => x is IReadOnlyDisabledFormComponent))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can also write it like this

foreach (var control in _formControls)
{
	if (control is IReadOnlyDisabledFormComponent readOnlyDisabledFormComponent)
	{
		readOnlyDisabledFormComponent.Disabled = _disabled;
		readOnlyDisabledFormComponent.StateHasChanged();
	}
}

Since you check in where clause is IReadOnlyDisabledFormComponent and then still use a direct cast, but up to you tbh, a really minor readability upgrade in exchange for more lines

@Mr-Technician
Copy link
Member Author

Mr-Technician commented Mar 23, 2023

The problem with my current interface approach is the user set ReadOnly/Disabled value is overwritten by the MudForm. This is in contrast to how the Radio Buttons inherit their Disabled state, for example, which hold a reference to the parent RadioGroup to check its Disabled state.

https://github.com/MudBlazor/MudBlazor/blob/dev/src/MudBlazor/Components/Radio/MudRadio.razor.cs#L141

I could do the same here but it would result in redundant code in every class that currently implements IReadOnlyDisabledFormComponent). The most efficient would be to refactor the interface to have an ParentDisabled and ParentReadOnly property like the Radio does, and replace all references to Disabled and ReadOnly, respectively.

The other alternative is to use @dennisrahmen's solution instead and provide no opportunity for overriding the Form value from the child (unless it is null, in which case it is ignored).

What are your thoughts @henon @ScarletKuro?

@Mr-Technician
Copy link
Member Author

An example of the code required in a child component:

internal bool IsReadOnly => ReadOnly || (Form != null && Form is MudForm mudForm && mudForm.ReadOnly);
internal bool IsDisabled => Disabled || (Form != null && Form is MudForm mudForm && mudForm.Disabled);

This is without adding the IsReadOnly and IsDisabled properties to the IForm interface so I'm forced to cast to MudForm, which compromises the loose-coupling somewhat. However, the other classes that implment IForm (namely, the table validator) would end up ignoring these properties anyway.

@henon
Copy link
Collaborator

henon commented Mar 24, 2023

The other alternative is to use @dennisrahmen's solution instead and provide no opportunity for overriding the Form value from the child (unless it is null, in which case it is ignored).

What are your thoughts @henon @ScarletKuro?

If I understand correctly @dennisrahmen and @ScarletKuro both suggested the same thing, Kuro specified it more exactly though. I agree that setting the form Disabled (or similarly Readonly) to disable or undisable all children regardless of their settings is probably the most useful use-case and what people would expect. Isn't this also consistent to how radio group works?

@dennisrahmen
Copy link
Contributor

dennisrahmen commented Mar 24, 2023

@henon yes but I pointed out that there should be a 'null' option because when I set the parameter Disabled on the parent with a variable it would always be either true or false.

Having it also null I could still use the individual components settings to set only one to Disable true.

@henon
Copy link
Collaborator

henon commented Mar 24, 2023

Absolutely, I agree.

@Mr-Technician
Copy link
Member Author

I agree that setting the form Disabled (or similarly Readonly) to disable or undisable all children regardless of their settings is probably the most useful use-case and what people would expect. Isn't this also consistent to how radio group works?

Yes. It just means that I can't set the respective property from the form and call it a day, as this would overwrite the child. I need code in each component like in my comment above that handles the required OR logic. It needs to check its own property and OR that with the property on the form, which doesn't work past the first render if the form overwrites the child property.

The easy route is to use null as the default state and still override the child properties when the parent property is not null. This might be confusing if someone sets Disabled=false on the Form and later tries to set Disabled=true on the child - After a render, I believe it would apply the Form value again and ignore that the value was set to true on the child.

@ScarletKuro
Copy link
Member

ScarletKuro commented Mar 24, 2023

I tried to make my own prototype without looking at MudBlazor implementation, so I'm not even sure it suits for MudBlazor.
I didn't think of anything better other than using CascadingValue on Disabled. Yet, I'm not certain if the behavior is transparent.
Lets say you have a form and 3 components like this:

<MudForm IsDisabled="isFormDisabled">
    <Component1/>
    <Component2 IsDisabled="true" />
    <Component3 IsDisabled="false" />
</MudForm>
  1. Component1 doesn't have any value set, so it's internal IsDisabled is false (default)
  2. Component2 has explicitly set IsDisabled=true
  3. Component3 has explicitly set IsDisabled=false
  • Initial isFormDisabled is set to false
    Behavior: You can freely change the state of Component1, Component2, Component3

  • isFormDisabled set to true
    Behavior: All components regarding of their state will have IsDisabled=true and it will be locked, you can't override the components IsDisabled as long as Form has IsDisabled=true

  • set isFormDisabled back to false
    Behavior: Component1 will get the previous state it had before the isFormDisabled=true. Component2 will have IsDisabled=true regardless of what state it had before, i.e. the initial value set in the html. Component3 same as Component2, just in reverse manner since it had IsDisabled=false.

If it's hard to read / understand here is the code to play with https://github.com/ScarletKuro/BlazorDisabled not really liking the solution since it violates BL0007, tho MudBlazor is overriding many [Parameter} auto-properties anyway.

Yeah, sounds that null route is easier and yet I'm also not sure about this one:

This might be confusing if someone sets Disabled=false on the Form and later tries to set Disabled=true on the child - After a render, I believe it would apply the Form value again and ignore that the value was set to true on the child.

UPD 3/25/2023! I slightly updated the code, and I also added alternative branch that uses OnParametersSetAsync, OnInitializedAsync and events but IsDisabled is still propagated via CascadingValue.

@Mr-Technician
Copy link
Member Author

@ScarletKuro Thanks so much for putting that example together! Sorry for not looking at it sooner, I didn't look over the weekend and wasn't working yesterday.

I think the cascading parameter version is probably the best option. There is also no interface requirement in this version because the Form never interacts with the ReadOnly/Disabled parameters. This also means it won't violate BL0007, though we have been doing this elsewhere anyway.

@Mr-Technician
Copy link
Member Author

@henon That should do it for the updated PR.

@henon henon merged commit f228506 into MudBlazor:dev Apr 1, 2023
@henon
Copy link
Collaborator

henon commented Apr 1, 2023

Seems like this was a lot of work, very much appreciated @Mr-Technician!

@Mr-Technician Mr-Technician deleted the feature/form-readonly-disabled branch April 1, 2023 14:13
ScarletKuro pushed a commit to ScarletKuro/MudBlazor that referenced this pull request Apr 4, 2023
* Add ReadOnly and Disabled properties to MudForm.
* Rework readonly/disabled handling of  all form components
@Ulf-Ason
Copy link
Contributor

A really useful and important PR. Thank you so much, @Mr-Technician! :-)

ilovepilav pushed a commit to ilovepilav/MudBlazor that referenced this pull request Nov 25, 2023
* Add ReadOnly and Disabled properties to MudForm.
* Rework readonly/disabled handling of  all form components
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

5 participants