Skip to content

Commit

Permalink
Scheduler drag and drop with mobile support (#1395)
Browse files Browse the repository at this point in the history
* Appointment drag and drop functionality for Scheduler component.

* Added drag support for mobile devices.

* Remove DragDropTouch.js.

* Remove touch events. Set ondragstart as an attribute (required for mobile support).

* Remove some views.

---------

Co-authored-by: Atanas Korchev <akorchev@gmail.com>
  • Loading branch information
V45370 and akorchev committed Mar 21, 2024
1 parent 311a53e commit b88156a
Show file tree
Hide file tree
Showing 19 changed files with 245 additions and 65 deletions.
17 changes: 17 additions & 0 deletions Radzen.Blazor/Common.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
using Microsoft.AspNetCore.Components.Web;
using Microsoft.Extensions.DependencyInjection;
using Radzen.Blazor;
using Radzen.Blazor.Rendering;
using System;
using System.Collections;
using System.Collections.Generic;
Expand Down Expand Up @@ -582,6 +583,22 @@ public class GoogleMapClickEventArgs
public GoogleMapPosition Position { get; set; }
}

/// <summary>
/// Supplies information about a <see cref="DropableViewBase.AppointmentMove" /> event that is being raised.
/// </summary>
public class SchedulerAppointmentMoveEventArgs
{
/// <summary>
/// Gets or sets the appointment data.
/// </summary>
public AppointmentData Appointment { get; set; }

/// <summary>
/// Gets or sets the time span.
/// </summary>
public TimeSpan TimeSpan { get; set; }
}

/// <summary>
/// Supplies information about a <see cref="RadzenMenu.Click" /> event that is being raised.
/// </summary>
Expand Down
12 changes: 10 additions & 2 deletions Radzen.Blazor/IScheduler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,11 @@ namespace Radzen.Blazor
/// </summary>
public interface IScheduler
{
/// <summary>
/// Gets or sets the appointment move event callback.
/// </summary>
/// <value>The appointment move event callback.</value>
EventCallback<SchedulerAppointmentMoveEventArgs> AppointmentMove { get; set; }
/// <summary>
/// Gets the appointments in the specified range.
/// </summary>
Expand Down Expand Up @@ -97,15 +102,18 @@ public interface IScheduler
/// </summary>
/// <param name="reference"></param>
/// <param name="data"></param>
/// <returns></returns>
Task MouseEnterAppointment(ElementReference reference, AppointmentData data);

/// <summary>
/// Returns true if the scheduler has a mouse enter appointment listener.
/// </summary>
/// <returns></returns>
bool HasMouseEnterAppointmentDelegate();

/// <summary>
/// Returns true if the scheduler has an AppointmentMove listener.
/// </summary>
bool HasAppointmentMoveDelegate();

/// <summary>
/// Notifies the scheduler that the user has moved the mouse out of the specified appointment.
/// </summary>
Expand Down
7 changes: 7 additions & 0 deletions Radzen.Blazor/ISchedulerView.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Components;

namespace Radzen.Blazor
Expand Down Expand Up @@ -49,5 +50,11 @@ public interface ISchedulerView
/// Gets the end date.
/// </summary>
DateTime EndDate { get; }
/// <summary>
/// Handles appointent move event.
/// </summary>
/// <param name="data"></param>
/// <returns></returns>
Task OnAppointmentMove(SchedulerAppointmentMoveEventArgs data);
}
}
5 changes: 3 additions & 2 deletions Radzen.Blazor/RadzenDayView.razor
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,11 @@
{
var appointments = Scheduler.GetAppointmentsInRange(StartDate, EndDate).ToList();

return
return
@<CascadingValue Value=@Scheduler>
<DayView StartDate=@StartDate EndDate=@EndDate StartTime=@StartTime EndTime=@EndTime Appointments=@appointments
TimeFormat=@TimeFormat MinutesPerSlot=@MinutesPerSlot />
TimeFormat=@TimeFormat MinutesPerSlot=@MinutesPerSlot
AppointmentMove=OnAppointmentMove />
</CascadingValue>
;
}
Expand Down
4 changes: 2 additions & 2 deletions Radzen.Blazor/RadzenMonthView.razor
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

@inherits SchedulerViewBase

@code {
@code {
public override RenderFragment Render()
{
var appointments = Scheduler.GetAppointmentsInRange(StartDate, EndDate);
Expand All @@ -21,7 +21,7 @@
}

return @<CascadingValue Value=@Scheduler>
<MonthView StartDate=@StartDate EndDate=@EndDate MaxAppointmentsInSlot=@maxAppointmentsInSlot MoreText=@MoreText Appointments=@appointments />
<MonthView StartDate=@StartDate EndDate=@EndDate MaxAppointmentsInSlot=@maxAppointmentsInSlot MoreText=@MoreText Appointments=@appointments AppointmentMove=OnAppointmentMove />
</CascadingValue>;
}
}
31 changes: 31 additions & 0 deletions Radzen.Blazor/RadzenScheduler.razor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -235,6 +235,32 @@ public partial class RadzenScheduler<TItem> : RadzenComponent, IScheduler
[Parameter]
public EventCallback<SchedulerLoadDataEventArgs> LoadData { get; set; }

/// <summary>
/// A callback that will be invoked when an appointment is being dragged and then dropped on a different slot.
/// Commonly used to change it to a different timeslot.
/// </summary>
/// <example>
/// <code>
/// &lt;RadzenScheduler Data=@appointments AppointmentMove=@OnAppointmentMove&gt;
/// &lt;/RadzenScheduler&gt;
/// @code {
/// async Task OnAppointmentMove(SchedulerAppointmentMoveEventArgs moved)
/// {
/// var draggedAppointment = appointments.SingleOrDefault(x => x == (Appointment)moved.Appointment.Data);
/// if (draggedAppointment != null)
/// {
/// draggedAppointment.Start = draggedAppointment.Start + moved.TimeSpan;
/// draggedAppointment.End = draggedAppointment.End + moved.TimeSpan;
/// await scheduler.Reload();
/// }
/// }
/// }
/// </code>
/// </example>
/// <value></value>
[Parameter]
public EventCallback<SchedulerAppointmentMoveEventArgs> AppointmentMove { get; set; }

IList<ISchedulerView> Views { get; set; } = new List<ISchedulerView>();

/// <summary>
Expand Down Expand Up @@ -610,5 +636,10 @@ bool IScheduler.HasMouseEnterAppointmentDelegate()
{
return AppointmentMouseEnter.HasDelegate;
}

bool IScheduler.HasAppointmentMoveDelegate()
{
return AppointmentMove.HasDelegate;
}
}
}
2 changes: 1 addition & 1 deletion Radzen.Blazor/RadzenWeekView.razor
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
var appointments = Scheduler.GetAppointmentsInRange(StartDate, EndDate).ToList();

return @<CascadingValue Value=@Scheduler>
<WeekView HeaderFormat=@HeaderFormat MinutesPerSlot=@MinutesPerSlot StartDate=@StartDate EndDate=@EndDate StartTime=@StartTime EndTime=@EndTime Appointments=@appointments TimeFormat="@TimeFormat" />
<WeekView HeaderFormat=@HeaderFormat MinutesPerSlot=@MinutesPerSlot StartDate=@StartDate EndDate=@EndDate StartTime=@StartTime EndTime=@EndTime Appointments=@appointments TimeFormat="@TimeFormat" AppointmentMove=OnAppointmentMove />
</CascadingValue>;
}
}
13 changes: 7 additions & 6 deletions Radzen.Blazor/RadzenYearPlannerView.razor
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

@inherits SchedulerViewBase

@code {
@code {
public override RenderFragment Render()
{
var appointments = Scheduler.GetAppointmentsInRange(StartDate, EndDate);
Expand All @@ -21,12 +21,13 @@
}

return @<CascadingValue Value=@Scheduler>
<YearPlannerView StartDate=@StartDate
EndDate=@EndDate
<YearPlannerView StartDate=@StartDate
EndDate=@EndDate
StartMonth=@StartMonth
MaxAppointmentsInSlot=@maxAppointmentsInSlot
MoreText=@MoreText
Appointments=@appointments />
MaxAppointmentsInSlot=@maxAppointmentsInSlot
MoreText=@MoreText
Appointments=@appointments
AppointmentMove=OnAppointmentMove />
</CascadingValue>;
}
}
3 changes: 2 additions & 1 deletion Radzen.Blazor/RadzenYearTimelineView.razor
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,8 @@
StartMonth=@StartMonth
MaxAppointmentsInSlot=@maxAppointmentsInSlot
MoreText=@MoreText
Appointments=@appointments />
Appointments=@appointments
AppointmentMove=OnAppointmentMove />
</CascadingValue>;
}
}
26 changes: 19 additions & 7 deletions Radzen.Blazor/Rendering/Appointment.razor
Original file line number Diff line number Diff line change
@@ -1,19 +1,23 @@
@using Radzen.Blazor
@using Radzen.Blazor.Rendering

<div @ref=@element class="@($"{"rz-event"} {CssClass}".Trim())" style=@Style @onclick=@OnClick @onmouseenter=@OnMouseEnter @onmouseleave=@OnMouseLeave>
<div class="rz-event-content" title=@Title @attributes=@Attributes>
@if (ShowAppointmentContent)
{
@Scheduler.RenderAppointment(Data)
}
<div @ref=@element class="@($"{"rz-event"} {CssClass}".Trim())" draggable=@DraggableAttribute style=@Style @onclick=@OnClick @onmouseenter=@OnMouseEnter @onmouseleave=@OnMouseLeave @ondrag=@OnDragStart ondragstart=@OnDragStartAttribute>
<div class="rz-event-content" title=@Title @attributes=@Attributes>
@if (ShowAppointmentContent)
{
@Scheduler.RenderAppointment(Data)
}
</div>
</div>
</div>
@code {
private ElementReference element;

private string Title => Scheduler.HasMouseEnterAppointmentDelegate() ? null : Data?.Text;

private string DraggableAttribute => Scheduler?.HasAppointmentMoveDelegate() == true ? "true" : null;

private string OnDragStartAttribute => Scheduler?.HasAppointmentMoveDelegate() == true ? "event.dataTransfer.setData('', event.target.id)" : null;

[Parameter]
public string CssClass { get; set; }

Expand All @@ -32,6 +36,9 @@
[Parameter]
public EventCallback<AppointmentData> Click { get; set; }

[Parameter]
public EventCallback<AppointmentData> DragStart { get; set; }

IDictionary<string, object> Attributes { get; set; }

[Parameter]
Expand Down Expand Up @@ -88,4 +95,9 @@
{
await Scheduler.MouseLeaveAppointment(element, Data);
}

public async Task OnDragStart(DragEventArgs args)
{
await DragStart.InvokeAsync(Data);
}
}
14 changes: 12 additions & 2 deletions Radzen.Blazor/Rendering/DaySlotEvents.razor
Original file line number Diff line number Diff line change
Expand Up @@ -33,12 +33,14 @@
@if (item.Start >= start && item.Start <= end)
{
<Appointment Data=@item Top=@top Left=@left Width=@width Height=@height Click=@OnAppointmentSelect
CssClass="@(CurrentDate.Date == date.Date && object.Equals(Appointments.Where(i => i.Start.Date == CurrentDate.Date).OrderBy(i => i.Start).ThenByDescending(i => i.End).ElementAtOrDefault(CurrentAppointment),item) ? "rz-state-focused" : "")" />
CssClass="@(CurrentDate.Date == date.Date && object.Equals(Appointments.Where(i => i.Start.Date == CurrentDate.Date).OrderBy(i => i.Start).ThenByDescending(i => i.End).ElementAtOrDefault(CurrentAppointment),item) ? "rz-state-focused" : "")"
DragStart=@OnAppointmentDragStart />
}
else if (date == StartDate)
{
<Appointment Data=@item Top=@top Left=@left Width=@width Height=@height Click=@OnAppointmentSelect
CssClass="@(CurrentDate == date && CurrentAppointment != - 1 && object.Equals(appointments.ElementAtOrDefault(CurrentAppointment),item) ? "rz-state-focused" : "")" />
CssClass="@(CurrentDate == date && CurrentAppointment != - 1 && object.Equals(appointments.ElementAtOrDefault(CurrentAppointment),item) ? "rz-state-focused" : "")"
DragStart=@OnAppointmentDragStart />
}
}
}
Expand All @@ -63,6 +65,9 @@
[Parameter]
public int MinutesPerSlot { get; set; }

[Parameter]
public EventCallback<AppointmentData> AppointmentDragStart { get; set; }

[Parameter]
public IList<AppointmentData> Appointments { get; set; }

Expand Down Expand Up @@ -136,4 +141,9 @@
return groups;
}

public async Task OnAppointmentDragStart(AppointmentData Data)
{
await AppointmentDragStart.InvokeAsync(Data);
}

}
8 changes: 5 additions & 3 deletions Radzen.Blazor/Rendering/DayView.razor
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
@using Radzen.Blazor
@inherits DropableViewBase

<div class="rz-view rz-day-view">
<div class="rz-view-header">
Expand All @@ -11,11 +12,11 @@
@onfocus=@(args => {if(CurrentDate == default(DateTime)) { CurrentDate = StartDate; }})>
<Hours Start=@StartTime End=@EndTime TimeFormat=@TimeFormat MinutesPerSlot=@MinutesPerSlot />
<div class="rz-slots">
<DaySlotEvents MinutesPerSlot=@MinutesPerSlot StartDate=@StartDate EndDate=@EndDate Appointments=@Appointments CurrentDate="@CurrentDate" CurrentAppointment="@currentAppointment"/>
<DaySlotEvents MinutesPerSlot=@MinutesPerSlot StartDate=@StartDate EndDate=@EndDate Appointments=@Appointments CurrentDate="@CurrentDate" CurrentAppointment="@currentAppointment" AppointmentDragStart=@OnAppointmentDragStart />
@for (var date = StartDate; date < EndDate; date = date.AddMinutes(MinutesPerSlot))
{
var slotDate = date;
<div @onclick=@(args => OnSlotClick(slotDate)) @attributes=@Attributes(date)></div>
<div @onclick=@(args => OnSlotClick(slotDate)) @attributes=@Attributes(date) ondragover="event.preventDefault();" @ondrop=@(args => @OnDrop(slotDate))></div>
}
</div>
</div>
Expand Down Expand Up @@ -67,6 +68,7 @@
{
var attributes = Scheduler.GetSlotAttributes(date, date.AddMinutes(MinutesPerSlot));
attributes["class"] = ClassList.Create("rz-slot").Add("rz-slot-minor", (date.Minute / MinutesPerSlot) % 2 == 1).Add(attributes).ToString();
attributes["dropzone"] = "move";
return attributes;
}

Expand Down Expand Up @@ -119,4 +121,4 @@
preventKeyPress = false;
}
}
}
}
54 changes: 54 additions & 0 deletions Radzen.Blazor/Rendering/DropableViewBase.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
using System;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Components;

namespace Radzen.Blazor.Rendering
{
/// <summary>
/// A base class for <see cref="MonthView" /> <see cref="DayView" /> <see cref="WeekView" /> <see cref="YearPlannerView" /> <see cref="YearTimelineView" /> views.
/// </summary>
public abstract class DropableViewBase : ComponentBase
{
private bool dragStarted = false;
private AppointmentData draggedAppointment;
/// <summary>
/// Gets or sets the appointment move event callback.
/// </summary>
/// <value>The appointment move event callback.</value>
[Parameter]
public EventCallback<SchedulerAppointmentMoveEventArgs> AppointmentMove { get; set; }

/// <summary>
/// Handles on slot drop.
/// </summary>
/// <param name="slotDate"></param>
/// <returns>Task</returns>
public async Task OnDrop(DateTime slotDate)
{
if (draggedAppointment != null)
{
TimeSpan timespan = slotDate - draggedAppointment.Start;

await AppointmentMove.InvokeAsync(new SchedulerAppointmentMoveEventArgs { Appointment = draggedAppointment, TimeSpan = timespan });

draggedAppointment = null;
}

dragStarted = false;
}

/// <summary>
/// Handles Appointment drag started.
/// </summary>
/// <param name="data"></param>
/// <returns></returns>
public void OnAppointmentDragStart(AppointmentData data)
{
if (!dragStarted)
{
dragStarted = true;
draggedAppointment = data;
}
}
}
}
Loading

0 comments on commit b88156a

Please sign in to comment.