Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions BlazorSlider/Components/App.razor
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@
<base href="/" />
@DxResourceManager.RegisterTheme(FluentLight)
@DxResourceManager.RegisterScripts()
<link href=@AppendVersion("https://cdn3.devexpress.com/jslib/25.1.3/css/dx.common.css") rel="stylesheet" />
<link href=@AppendVersion("https://cdn3.devexpress.com/jslib/25.1.3/css/dx.fluent.blue.light.css") rel="stylesheet" />
<link href=@AppendVersion("css/site.css") rel="stylesheet" />
<link href=@AppendVersion("BlazorSlider.styles.css") rel="stylesheet" />
<HeadOutlet />
Expand Down
63 changes: 59 additions & 4 deletions BlazorSlider/Components/Pages/Index.razor
Original file line number Diff line number Diff line change
@@ -1,6 +1,61 @@
@page "/"
<PageTitle>Welcome</PageTitle>
@rendermode InteractiveServer

<div class="main-content">
Welcome to your new DevExpress Blazor Application
</div>
@using BlazorSlider.Components.Slider

<div>
<DxFormLayout CssClass="slider-fl" ItemCaptionAlignment="ItemCaptionAlignment.All">
<DxFormLayoutGroup ColSpanXs="12">
<Items>
<DxFormLayoutItem ColSpanXs="12" Caption="Default mode">
<DxSlider T="int" MinValue="0" MaxValue="100" Value="90" />
</DxFormLayoutItem>
<DxFormLayoutItem ColSpanXs="12" Caption="With labels">
<DxSlider T="int" MinValue="0" MaxValue="100" Value="50">
<DxSliderLabelSettings Visible="true"
Position="VerticalEdge.Top" />
</DxSlider>
</DxFormLayoutItem>
<DxFormLayoutItem ColSpanXs="12" Caption="With tooltip">
<DxSlider T="int" MinValue="0" MaxValue="100" Value="35">
<DxSliderTooltipSettings Enabled="true"
ShowMode="TooltipShowMode.Always"
Position="VerticalEdge.Bottom" />
</DxSlider>
</DxFormLayoutItem>
<DxFormLayoutItem ColSpanXs="12" Caption="Without range highlighting">
<DxSlider T="int" MinValue="0" MaxValue="100"
Value="20" ShowRange="false" />
</DxFormLayoutItem>
<DxFormLayoutItem ColSpanXs="12" Caption="With discrete step">
<DxSlider T="int" MinValue="0" MaxValue="100" Value="10" Step="10">
<DxSliderTooltipSettings Enabled="true" />
</DxSlider>
</DxFormLayoutItem>
<DxFormLayoutItem ColSpanXs="12" Caption="Disabled">
<DxSlider T="int" MinValue="0" MaxValue="100"
Value="50" Enabled="false" />
</DxFormLayoutItem>
</Items>
</DxFormLayoutGroup>
<DxFormLayoutGroup Caption="Process Value Changes" ColSpanXs="12">
<Items>
<DxFormLayoutItem ColSpanXs="12" Caption="On handle movement">
<DxSlider @bind-Value="SliderValue" MinValue="0" MaxValue="100"
ValueChangeMode="SliderValueChangeMode.OnHandleMove" />
</DxFormLayoutItem>
<DxFormLayoutItem ColSpanXs="12" Caption="On handle release">
<DxSlider @bind-Value="SliderValue" MinValue="0" MaxValue="100"
ValueChangeMode="SliderValueChangeMode.OnHandleRelease" />
</DxFormLayoutItem>
<DxFormLayoutItem ColSpanXs="12" Caption="Slider value">
<DxSpinEdit @bind-Value="SliderValue" />
</DxFormLayoutItem>
</Items>
</DxFormLayoutGroup>
</DxFormLayout>
</div>

@code{
double SliderValue { get; set; } = 15;
}
70 changes: 5 additions & 65 deletions BlazorSlider/Components/Pages/Index.razor.css
Original file line number Diff line number Diff line change
@@ -1,66 +1,6 @@
::deep .welcome-gridlayout {
margin: auto;
width: auto;
height: auto;
::deep .slider-fl {
max-height: 100vh;
overflow-y: auto;
overflow-x: hidden;
padding: 1rem;
}

::deep .welcome-gridlayout .dxbl-gridlayout-root {
align-content: center;
justify-content: center;
}

::deep .title {
text-align: center;
}

::deep .welcome-cards {
display: flex;
flex-wrap: wrap;
gap: 1.5rem;
justify-content: center;
}

::deep .welcome-card {
width: 26.25rem;
height: 15rem;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
box-shadow: 0px 4px 6px -1px rgba(0, 0, 0, 0.1), 0px 2px 4px -2px rgba(0, 0, 0, 0.1);
transition: box-shadow 0.2s;
border-radius: 1rem;
color: var(--bs-link-color, var(--DS-primary-90));
gap: 1.5rem;
text-decoration: none;
position: relative;
}

::deep .welcome-card:hover {
box-shadow: 0px 20px 25px -5px rgba(0, 0, 0, 0.1), 0px 8px 10px -6px rgba(0, 0, 0, 0.1);
}

::deep .welcome-card .welcome-card-img {
width: 6.5rem;
height: 6.5rem;
}

::deep .welcome-card .welcome-card-text {
font-size: 1.75rem;
font-weight: 600;
letter-spacing: 0em;
text-align: center;
text-decoration: unset;
}

::deep .welcome-card .welcome-card-back {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: var(--bs-body-color, var(--DS-color-content-neutral-default-rest));
opacity: 0.05;
border-radius: 1rem;
z-index: -2;
}
126 changes: 126 additions & 0 deletions BlazorSlider/Components/Slider/DxSlider.razor
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
@inject IJSRuntime JS
@attribute [CascadingTypeParameter(nameof(T))]
@typeparam T where T : struct
@implements IAsyncDisposable

<CascadingValue IsFixed="true" Value="sliderState">
@ChildContent
<div @ref="sliderElement"></div>
</CascadingValue>

@code {
#region Fields
readonly SliderState<T> sliderState = new();
T prevValue;
T internalValue;
ElementReference sliderElement;
DotNetObjectReference<DxSlider<T>>? dotNetRef;
IJSObjectReference? clientModule;
IJSObjectReference? clientSlider;
#endregion

#region Properties
DotNetObjectReference<DxSlider<T>> DotNetRef
=> dotNetRef ??= DotNetObjectReference.Create(this);
#endregion

#region Parameters
[Parameter]
public RenderFragment? ChildContent { get; set; }

[Parameter]
public T MinValue { get; set; }

[Parameter]
public T MaxValue { get; set; }

[Parameter]
public T? Step { get; set; }

[Parameter]
public T Value { get; set; }

[Parameter]
public EventCallback<T> ValueChanged { get; set; }

[Parameter]
public SliderValueChangeMode ValueChangeMode { get; set; }

[Parameter]
public bool ShowRange { get; set; } = true;

[Parameter]
public bool Enabled { get; set; } = true;
#endregion

#region Lifecycle Events
protected override async Task OnParametersSetAsync() {
if(!Value.Equals(prevValue)) {
internalValue = Value;
prevValue = Value;
}
await UpdateServerState();
await UpdateClientState();
}

protected override async Task OnAfterRenderAsync(bool firstRender) {
if(firstRender) {
await JS.LoadDxResources();
clientModule = await LoadSliderModule();
clientSlider = await LoadClientInstance(clientModule);
}
}

async ValueTask IAsyncDisposable.DisposeAsync() {
if(clientSlider != null)
await clientSlider.DisposeAsync();
if(clientModule != null)
await clientModule.DisposeAsync();
DotNetRef?.Dispose();
}
#endregion

#region Methods
[JSInvokable]
public async Task UpdateValueFromClient(T clientValue) {
if(!internalValue.Equals(clientValue)) {
internalValue = clientValue;
await UpdateServerState();
await ValueChanged.InvokeAsync(clientValue);
}
}

ValueTask UpdateServerState() {
sliderState.Value = internalValue;
sliderState.Step = Step;
sliderState.ShowRange = ShowRange;
sliderState.MinValue = MinValue;
sliderState.MaxValue = MaxValue;
sliderState.Enabled = Enabled;
sliderState.ValueChangeMode = ValueChangeMode;
return ValueTask.CompletedTask;
}

ValueTask UpdateClientState() {
if(clientSlider != null && clientModule != null) {
return clientModule.InvokeVoidAsync("updateStateFromServer",
clientSlider, sliderState);
}
return ValueTask.CompletedTask;
}

ValueTask<IJSObjectReference> LoadSliderModule() {
return JS.InvokeAsync<IJSObjectReference>(
"import", "./Components/Slider/DxSlider.razor.js");
}

ValueTask<IJSObjectReference> LoadClientInstance(IJSObjectReference module) {
return module.InvokeAsync<IJSObjectReference>(
"initializeSlider",
sliderElement,
DotNetRef,
sliderState
);
}
#endregion
}
72 changes: 72 additions & 0 deletions BlazorSlider/Components/Slider/DxSlider.razor.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
let updateInvokedByServer = false;
let serverIsProcessing = false;
let queuedValue = null;

export async function initializeSlider(element, dotNet, sliderState) {
return new DevExpress.ui.dxSlider(element, {
min: sliderState.minValue,
max: sliderState.maxValue,
value: sliderState.value,
step: sliderState.step === null ? 1 : sliderState.step,
showRange: sliderState.showRange,
disabled: !sliderState.enabled,
valueChangeMode: decapitalize(sliderState.valueChangeMode),
label: {
visible: sliderState.labelVisible,
format(value) {
return `${value}%`;
},
position: decapitalize(sliderState.labelPosition)
},
tooltip: {
enabled: sliderState.tooltipEnabled,
format(value) {
return `${value}%`;
},
showMode: decapitalize(sliderState.tooltipShowMode),
position: decapitalize(sliderState.tooltipPosition)
},
onValueChanged({ value }) {
if(!updateInvokedByServer) {
scheduleServerValueUpdate(dotNet, value);
}
},
});
}

export async function updateStateFromServer(slider, state) {
updateInvokedByServer = true;
slider.option("value", state.value);
slider.option("min", state.minValue);
slider.option("max", state.maxValue);
slider.option("step", state.step === null ? 1 : state.step);
slider.option("showRange", state.showRange);
slider.option("disabled", !state.enabled);
slider.option("valueChangeMode", decapitalize(state.valueChangeMode));
updateInvokedByServer = false;
}

function decapitalize(word) {
return word[0].toLowerCase() + word.slice(1);
}

async function scheduleServerValueUpdate(dotNetRef, value) {
if(serverIsProcessing) {
queuedValue = value;
return;
}

serverIsProcessing = true;

try {
await dotNetRef.invokeMethodAsync("UpdateValueFromClient", value);
}
finally {
serverIsProcessing = false;
if(queuedValue !== null) {
const v = queuedValue;
queuedValue = null;
scheduleServerValueUpdate(dotNetRef, v);
}
}
}
19 changes: 19 additions & 0 deletions BlazorSlider/Components/Slider/DxSliderLabelSettings.razor
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
@typeparam T where T : struct
@code {
[CascadingParameter]
private SliderState<T>? SliderState { get; set; }

[Parameter]
public bool Visible { get; set; }

[Parameter]
public VerticalEdge Position { get; set; }

protected override void OnInitialized()
{
if(SliderState is null)
return;
SliderState.LabelVisible = Visible;
SliderState.LabelPosition = Position;
}
}
22 changes: 22 additions & 0 deletions BlazorSlider/Components/Slider/DxSliderTooltipSettings.razor
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
@typeparam T where T : struct
@code {
[CascadingParameter]
private SliderState<T>? SliderState { get; set; }

[Parameter]
public bool Enabled { get; set; }

[Parameter]
public VerticalEdge Position { get; set; }

[Parameter]
public TooltipShowMode ShowMode { get; set; }

protected override void OnInitialized() {
if(SliderState is null)
return;
SliderState.TooltipEnabled = Enabled;
SliderState.TooltipPosition = Position;
SliderState.TooltipShowMode = ShowMode;
}
}
Loading
Loading