Reputation: 828
I've made a range input in a blazor component. And now i'm trying to make a logarithme range input.
To do this, I wanted to use my first range input, and simply make a wrapper between a fake value in the range input and the real value.
but I think I havent well understood the component binding, the index page is not notified of modifications.
Here is the range input component:
<div id="rc-@ID">
@(new MarkupString($@"<style>
#rc-{ID} {{
position: relative;
width: 100%;
}}
#rc-{ID} > input[type='range'] {{
padding: 0;
margin: 0;
display: inline-block;
vertical-align: top;
width: 100%;
--range-color: hsl(211deg 100% 50%);
background: var(--track-background);
}}
#rc-{ID} > input[type='range']::-moz-range-track {{
border-color: transparent; /* needed to switch FF to 'styleable' control */
}}
#rc-{ID} > input[name='low-range'] {{
position: absolute;
}}
#rc-{ID} > input[name='low-range']::-webkit-slider-thumb {{
position: relative;
z-index: 2;
}}
#rc-{ID} > input[name='low-range']::-moz-range-thumb {{
transform: scale(1); /* FF doesn't apply position it seems */
z-index: 1;
}}
#rc-{ID} > input[name='high-range'] {{
position: relative;
--track-background: linear-gradient(to right, transparent {(int)(100 * (LowerValue - MinBound) / (MaxBound - MinBound) + 1 + 0.5f)}%, var(--range-color) 0, var(--range-color) {(int)(100 * (HigherValue - MinBound) / (MaxBound - MinBound) - 1 + 0.5f)}%, transparent 0 ) no-repeat 0 50% / 100% 100%;
background: linear-gradient(to right, gray {(int)(100 * (LowerValue - MinBound) / (MaxBound - MinBound) + 1+ 0.5f)}%, transparent 0, transparent {(int)(100 * (HigherValue - MinBound) / (MaxBound - MinBound) - 1 + 0.5f)}%, gray 0 ) no-repeat 0 50% / 100% 30%
}}
#rc-{ID} > input[type='range']::-webkit-slider-runnable-track {{
background: var(--track-background);
}}
#rc-{ID} > input[type='range']::-moz-range-track {{
background: var(--track-background);
}}
</style>"))
<input class="custom-range" name="low-range" type="range" min="@MinBound" max="@MaxBound" step="@Step" @bind="@LowerValue" @bind:event="oninput" />
<input class="custom-range" name="high-range" type="range" min="@MinBound" max="@MaxBound" step="@Step" @bind="@HigherValue" @bind:event="oninput" />
</div>
@code
{
[Parameter] public float MinBound { get; set; } = 0;
[Parameter] public float MaxBound { get; set; } = 1;
[Parameter] public float Step { get; set; } = 0.01f;
[Parameter]
public float? ValueLow
{
get
{
var res = Math.Min(_valueLow, _valueHigh);
if (res == MinBound)
return null;
return res;
}
set
{
if (!value.HasValue)
{
if (_valueLow.Equals(MinBound))
return;
_valueLow = MinBound;
}
else
{
if (_valueLow.Equals(value.Value))
return;
_valueLow = value.Value;
}
if (_valueLow > _valueHigh)
{
_valueLow = _valueHigh;
_valueHigh = value.Value;
ValueHighChanged.InvokeAsync(_valueHigh);
}
if (_valueLow == MinBound)
ValueLowChanged.InvokeAsync(null);
else
ValueLowChanged.InvokeAsync(_valueLow);
}
}
[Parameter]
public float? ValueHigh
{
get
{
var res = Math.Max(_valueLow, _valueHigh);
if (res == MaxBound)
return null;
return res;
}
set
{
if (!value.HasValue)
{
if (_valueHigh.Equals(MaxBound))
return;
_valueHigh = MaxBound;
}
else
{
if (_valueHigh.Equals(value.Value))
return;
_valueHigh = value.Value;
}
if (_valueLow > _valueHigh)
{
_valueHigh = _valueLow;
_valueLow = value.Value;
ValueLowChanged.InvokeAsync(_valueLow);
}
if (_valueHigh == MaxBound)
ValueHighChanged.InvokeAsync(null);
else
ValueHighChanged.InvokeAsync(_valueHigh);
}
}
[Parameter] public EventCallback<float?> ValueLowChanged { get; set; }
[Parameter] public EventCallback<float?> ValueHighChanged { get; set; }
float _valueLow = 0;
float _valueHigh = 1;
private float LowerValue
{
get => Math.Min(_valueLow, _valueHigh);
set => ValueLow = value;
}
private float HigherValue
{
get => Math.Max(_valueLow, _valueHigh);
set => ValueHigh = value;
}
string ID = Guid.NewGuid().ToString().Replace("-", "").Substring(15);
}
And here is my Range input log component:
<RangeControl @bind-ValueLow="Low"
@bind-ValueHigh="High"
MaxBound="max"
MinBound="min"
Step="1" />
<div class="d-flex">
<strong>Log values : </strong>
<span>@Low</span>
<span class="ml-2">@High</span>
</div>
@code
{
private float min = 1.0f;
private float max = 100.0f;
[Parameter] public float MinBound { get; set; } = 10;
[Parameter] public float MaxBound { get; set; } = 10000;
[Parameter] public float Step { get; set; } = 1;
private float r => MinBound == 0 ? MaxBound : (MaxBound / MinBound);
private float? _valueLow;
[Parameter]
public float? ValueLow
{
get => _valueLow;
set
{
if (value == _valueLow) return;
_valueLow = value;
ValueLowChanged.InvokeAsync(ValueLow);
}
}
private float? _valueHigh;
[Parameter]
public float? ValueHigh
{
get => _valueHigh;
set
{
if (value == _valueHigh) return;
_valueHigh = value;
ValueHighChanged.InvokeAsync(ValueHigh);
}
}
private float? Low
{
get
{
if (ValueLow.HasValue)
return (float)((min = max) * Math.Log(ValueLow.Value) / Math.Log(r));
return null;
}
set
{
if (value.HasValue)
ValueLow = (float)Math.Exp(value.Value * Math.Log(r) / (max - min));
else
ValueLow = null;
}
}
private float? High
{
get
{
if (ValueHigh.HasValue)
return (float)((min = max) * Math.Log(ValueHigh.Value) / Math.Log(r));
return null;
}
set
{
if (value.HasValue)
ValueHigh = (float)Math.Exp(value.Value * Math.Log(r) / (max - min));
else
ValueHigh = null;
}
}
[Parameter] public EventCallback<float?> ValueLowChanged { get; set; }
[Parameter] public EventCallback<float?> ValueHighChanged { get; set; }
}
And the index page :
@page "/"
<h1>Hello, world!</h1>
<RangeControl @bind-ValueHigh="ValueHigh" @bind-ValueLow="ValueLow" MinBound="10" MaxBound="10000" Step="1"></RangeControl>
<br />
<RangeControlLog @bind-ValueHigh="ValueHigh" @bind-ValueLow="ValueLow" MinBound="10" MaxBound="10000" Step="1"></RangeControlLog>
<div class="d-flex">
<strong>Real values : </strong>
<span>@ValueLow</span>
<span class="ml-2">@ValueHigh</span>
</div>
@code {
float? ValueHigh = null;
float? ValueLow = null;
}
Upvotes: 6
Views: 2785
Reputation: 31565
You cannot have nested @bind-
i.e. have a wrapper that uses @bind-
of the wrapped component and also expose a property to be used with @bind-
.
You need to pass Foo
and FooChanged
to the component being wrapped.
This means that in your RangeControlLog
, you need to pass to RangeControl
ValueLow
and ValueLowChanged
instead of using @bind-ValueLow
<RangeControl ValueLow="Low"
ValueHigh="High"
ValueLowChanged="ValueLowChanged"
ValueHighChanged="ValueHighChanged"
MaxBound="max"
MinBound="min"
Step="1" />
To learn more, you can take a look at the docs about chained binding and also take a look at this question I have made to understand better about ValueChanged
and how it works.
But for short, when you use @bind-Foo="Bar"
it transforms it into Foo="Bar"
, FooChanged="@(foo => Bar = foo;)"
which are kind of a default value for updating the properties. But it doesn't work when you have multiple @bind-
, so you need to pass that directly.
For me, @bind-
looks like a syntax sugar for binding properties and when you have the parameters Foo
and FooChanged
, you can use @bind-Foo
.
Upvotes: 5