I'm in the same situation as the OP in this post: https://www.telerik.com/forums/how-do-you-always-edit-series-when-editing-a-recurring-appointment
In my case, I'm working with with the Blazor Scheduler Component. How can I disable the prompt and automatically select "Edit the series"? Or at least "disable" the "Edit this occurrence" button when updating/deleting an appointment?
---
ADMIN EDIT
Note: You may also check the Differentiate "edit current occurrence" and "edit the series" in the RecurrenceDialog feature request as the implementation of both features will most likely be covered in one release.
Please comment with suggestions on how you would like to see this feature exposed. Ideally it might have to be dynamic so you can toggle it based on conditions, so perhaps it could be a parameter (not flexible), an event argument to OnEdit, or a separate event/setting.
Here is a sample code you can try to achieve this - comments in the code explain how the templates are used to capture the edit action and some JS is used to fake-click the button to simulate a user choice:
@inject IJSRuntime _jsInterop
@* this errors suppression is a hack to make this sample succinct
move this script to a proper place for a real app*@
<script suppress-error="BL9992">
function clickSchedulerPromptButton(btnIndex) {
setTimeout(function () {
var buttons = document.querySelectorAll(".k-window-content .text-right button");
if (buttons && buttons.length >= btnIndex) {
var chosenButton = buttons[btnIndex];
chosenButton.click();
}
}, 50);
}
</script>
@* appearance setting for the template - make the custom template tall as the appointment to capture all clicks *@
<style>
.tallAppt {
height: 100%;
}
</style>
<TelerikScheduler Data="@Appointments"
OnUpdate="@UpdateAppointment"
OnCreate="@AddAppointment"
OnDelete="@DeleteAppointment"
AllowCreate="true" AllowDelete="true" AllowUpdate="true"
@bind-Date="@StartDate" Height="600px" @bind-View="@CurrView">
<ItemTemplate>
@{
SchedulerAppointment appt = context as SchedulerAppointment;
}
<div title="@appt.Start - @appt.End" class="tallAppt" @ondblclick="@( () => ChooseEditMode(appt) )"><div class="k-event-template">@appt.Title</div></div>
</ItemTemplate>
<AllDayItemTemplate>
@{
SchedulerAppointment appt = context as SchedulerAppointment;
}
<div title="@appt.Start.ToShortDateString() - @appt.Title" class="tallAppt" @ondblclick="@( () => ChooseEditMode(appt) )"><div class="k-event-template">@appt.Title</div></div>
</AllDayItemTemplate>
<SchedulerViews>
<SchedulerDayView StartTime="@DayStart" />
<SchedulerWeekView StartTime="@DayStart" />
<SchedulerMultiDayView StartTime="@DayStart" NumberOfDays="10" />
</SchedulerViews>
</TelerikScheduler>
@code {
//async void so we don't block the execution
//we will have a small timeout in the script to let it wait for the popup
async void ChooseEditMode(SchedulerAppointment appt)
{
// check if we have a recurring appointment or a member of one
if (appt.RecurrenceId != null || !string.IsNullOrEmpty(appt.RecurrenceRule))
{
int btnIndexToClick = 0;//the first button - edit instance
// make it 1 for the second button - the series
await _jsInterop.InvokeVoidAsync("clickSchedulerPromptButton", btnIndexToClick);
}
}
//the rest is sample data and sample CUD operations handling
// sample data and scheduler settings
public SchedulerView CurrView { get; set; } = SchedulerView.Week;
public DateTime StartDate { get; set; } = new DateTime(2019, 12, 2);
public DateTime DayStart { get; set; } = new DateTime(2000, 1, 1, 8, 0, 0); //the time portion is important
List<SchedulerAppointment> Appointments { get; set; }
async Task UpdateAppointment(SchedulerUpdateEventArgs args)
{
SchedulerAppointment item = (SchedulerAppointment)args.Item;
await MyService.Update(item);
await GetSchedulerData();
}
async Task AddAppointment(SchedulerCreateEventArgs args)
{
SchedulerAppointment item = args.Item as SchedulerAppointment;
await MyService.Create(item);
await GetSchedulerData();
}
async Task DeleteAppointment(SchedulerDeleteEventArgs args)
{
SchedulerAppointment item = (SchedulerAppointment)args.Item;
await MyService.Delete(item);
await GetSchedulerData();
}
public class SchedulerAppointment
{
public Guid Id { get; set; }
public string Title { get; set; }
public string Description { get; set; }
public DateTime Start { get; set; }
public DateTime End { get; set; }
public bool IsAllDay { get; set; }
public string RecurrenceRule { get; set; }
public List<DateTime> RecurrenceExceptions { get; set; }
public Guid? RecurrenceId { get; set; }
public SchedulerAppointment()
{
Id = Guid.NewGuid();
}
}
async Task GetSchedulerData()
{
Appointments = await MyService.Read();
}
protected override async Task OnInitializedAsync()
{
await GetSchedulerData();
}
// the following static class mimics an actual data service that handles the actual data source
// replace it with your actual service through the DI, this only mimics how the API can look like and works for this standalone page
public static class MyService
{
private static List<SchedulerAppointment> _data { get; set; } = new List<SchedulerAppointment>()
{
new SchedulerAppointment
{
Title = "Board meeting",
Description = "Q4 is coming to a close, review the details.",
Start = new DateTime(2019, 12, 5, 10, 00, 0),
End = new DateTime(2019, 12, 5, 11, 30, 0)
},
new SchedulerAppointment
{
Title = "Vet visit",
Description = "The cat needs vaccinations and her teeth checked.",
Start = new DateTime(2019, 12, 2, 11, 30, 0),
End = new DateTime(2019, 12, 2, 12, 0, 0)
},
new SchedulerAppointment
{
Title = "Planning meeting",
Description = "Kick off the new project.",
Start = new DateTime(2019, 12, 6, 9, 30, 0),
End = new DateTime(2019, 12, 6, 12, 45, 0)
},
new SchedulerAppointment
{
Title = "Trip to Hawaii",
Description = "An unforgettable holiday!",
IsAllDay = true,
Start = new DateTime(2019, 11, 27),
End = new DateTime(2019, 12, 05)
},
new SchedulerAppointment
{
Title = "Morning run",
Description = "Some time to clear the head and exercise.",
Start = new DateTime(2019, 11, 27, 9, 0, 0),
End = new DateTime(2019, 11, 27, 9, 30, 0),
RecurrenceRule = "FREQ=WEEKLY;BYDAY=MO,TU,WE,TH,FR"
}
};
public static async Task Create(SchedulerAppointment itemToInsert)
{
itemToInsert.Id = Guid.NewGuid();
_data.Insert(0, itemToInsert);
}
public static async Task<List<SchedulerAppointment>> Read()
{
return await Task.FromResult(_data);
}
public static async Task Update(SchedulerAppointment itemToUpdate)
{
var index = _data.FindIndex(i => i.Id == itemToUpdate.Id);
if (index != -1)
{
_data[index] = itemToUpdate;
}
}
public static async Task Delete(SchedulerAppointment itemToDelete)
{
if (itemToDelete.RecurrenceId != null)
{
// a recurrence exception was deleted, you may want to update
// the rest of the data source - find an item where theItem.Id == itemToDelete.RecurrenceId
// and remove the current exception date from the list of its RecurrenceExceptions
}
if (!string.IsNullOrEmpty(itemToDelete.RecurrenceRule) && itemToDelete.RecurrenceExceptions?.Count > 0)
{
// a recurring appointment was deleted that had exceptions, you may want to
// delete or update any exceptions from the data source - look for
// items where theItem.RecurrenceId == itemToDelete.Id
}
_data.Remove(itemToDelete);
}
}
}
---
When adding / editing the last time slot for the day, SchedulerEditEventArgs has wrong End value.
For example Start value is 28.12.2023. 23:30:00 and end value is 28.12.2023. 00:00:00. Should instead be 28.12.2023. 23:59:59 or 29.12.2023. 00:00:00.
When I change the date while I'm in the month view, the slot doesn't change to the newly calculated ItemsPerSlot value.
To reproduce, open the calendar and set the date to the 3rd of December: REPL link.
I'd like to have a data virtualization feature (similar to the OnRead event), so I can load only small chunks of data (appointments) specifically for the current page/view.
===
ADMIN EDIT
===
Currently, you can achieve similar functionality by handling the DateChanged and ViewChanged events of the Scheduler to load only the relevant appointments for the selected period.
You can find an example here: Load Scheduler Appointments on Demand.
The Timeline view currently does not provide the "Show business hours" option as in the other views.
Please add that for the Timeline view similar to the jQuery version of the component.
===
ADMIN EDIT
===
For the time being, you can achieve this functionality with a custom approach by programmatically setting the StartTime and EndTime of the view to equal the WorkDayStart and WorkDayEnd.
Here is a basic example: https://blazorrepl.telerik.com/QyEhvjbO07j1ZvID42.
Hi,
I'm building a Grid which contains a nested Grid in it's `<DetailTemplate/>`.
When I'm opening the details, the grid events fire, the data gets loaded correctly but the data doesn't show up in the UI. Using .NET 8 Blazor with Telerik UI for Blazor Version 6.0.2.
This is the grid containing the Grid (data here gets loaded correctly):
<TelerikGrid @ref="@_grid" TItem="ChecklistDetailModel" Pageable Page="@_settings.CurrentPageNumber" PageSize="@_settings.Limit" OnRead="@OnPageReadAsync" Width="100%" Height="100%">
<GridToolBarTemplate>
<LawAreaSelect SelectedLawAreaId="_settings.SelectedLawAreaId" SelectedCountryId="_settings.SelectedCountryId" OnLawAreaSelected="OnLawAreaSelected" Width="250px" />
<CountriesSelect Value="_settings.SelectedCountryId" OnChangeCallback="OnCountrySelected" Width="250px" />
<TelerikButton Icon="FontIcon.FileAdd" OnClick="OnCreateChecklistButtonClick" Title="@Localizer["ChecklistOverview.Create"]" />
</GridToolBarTemplate>
<GridColumns>
<GridColumn Title="ID" Width="50px">
<Template Context="checklist">
@{
var model = checklist as ChecklistDetailModel;
<span>@model.Id</span>
}
</Template>
</GridColumn>
<GridColumn Title="@Localizer["Data.LawArea"]">
<Template Context="checklist">
@{
var model = checklist as ChecklistDetailModel;
<span>@model.LawArea.Name</span>
}
</Template>
</GridColumn>
<GridColumn Title="@Localizer["Data.Country"]">
<Template Context="checklist">
@{
var model = checklist as ChecklistDetailModel;
<span>@model.Country.Name</span>
}
</Template>
</GridColumn>
<GridColumn Title="@Localizer["Data.Checklist.Revision"]">
<Template Context="checklist">
@{
var model = checklist as ChecklistDetailModel;
@if (model.RevisionName != null)
{
<span>@model.RevisionName</span>
}
else
{
<i>@Localizer["Data.Checklist.NoRevision"]</i>
}
}
</Template>
</GridColumn>
<GridColumn Title="@Localizer["Data.ChecklistRevisionDate"]">
<Template Context="checklist">
@{
var model = checklist as ChecklistDetailModel;
@if (model.LastUpdated.HasValue)
{
<span>@model.LastUpdated.Value.ToShortDateString()</span>
}
else
{
<span>@model.CreatedDate.ToShortDateString()</span>
}
}
</Template>
</GridColumn>
<GridCommandColumn>
<GridCommandButton Icon="FontIcon.EditTools" OnClick="OnEditChecklistButtonClick" />
<GridCommandButton Icon="FontIcon.PaperPlane" OnClick="OnAssignChecklistButtonClick" />
</GridCommandColumn>
</GridColumns>
<DetailTemplate Context="checklist">
<CustomerChecklistGrid ChecklistId="checklist.Id" OnEditCustomerChecklistButtonClick="OnEditCustomerChecklistButtonClick" OnNavigateToCustomerChecklistButtonClick="OnNavigateToCustomerChecklistButtonClick" />
</DetailTemplate>
</TelerikGrid>
This is the `CustomerChecklistGrid` code inserted into the DetailTemplate of the Grid above:
<TelerikGrid @ref="_grid" TItem="CustomerChecklistModel" Pageable Page="_settings.CurrentPageNumber" PageSize="_settings.Limit" OnRead="OnPageReadAsync">
<GridToolBarTemplate>
<CustomerSelect OnCustomerSelected="OnCustomerSelected" SelectedCustomerId="_settings.SelectedCustomerId" Width="250px" />
</GridToolBarTemplate>
<GridColumns>
<GridColumn Title="ID" Width="50px">
<Template Context="customerChecklist">
@{
var model = customerChecklist as CustomerChecklistModel;
<span>@model.Id</span>
}
</Template>
</GridColumn>
<GridColumn Title="@Localizer["Data.Customer"]">
<Template Context="customerChecklist">
@{
var model = customerChecklist as CustomerChecklistModel;
<span>@model.Customer.Name</span>
}
</Template>
</GridColumn>
<GridColumn Title="@Localizer["ChecklistsOverview.CompletedOn"]">
<Template Context="customerChecklist">
@{
var model = customerChecklist as CustomerChecklistModel;
@if (model.CompletedOn.HasValue)
{
<span>@model.CompletedOn.Value.ToShortDateString()</span>
}
else
{
<i>@Localizer["ChecklistsOverview.NotCompleted"]</i>
}
}
</Template>
</GridColumn>
<GridCommandColumn Context="customerChecklist">
<GridCommandButton Icon="FontIcon.EditTools" OnClick="OnEditCustomerChecklistButtonClickAsync" />
@{
var model = customerChecklist as CustomerChecklistModel;
if (!model.CompletedOn.HasValue)
{
<GridCommandButton Icon="FontIcon.PaperPlane" OnClick="OnNavigateToCustomerChecklistButtonClickAsync" />
}
}
</GridCommandColumn>
</GridColumns>
</TelerikGrid>
This is the backing C# code for the grid:
public partial class CustomerChecklistGrid
{
[Parameter] public int ChecklistId { get; set; }
[Parameter] public EventCallback<CustomerChecklistModel> OnEditCustomerChecklistButtonClick { get; set; }
[Parameter] public EventCallback<CustomerChecklistModel> OnNavigateToCustomerChecklistButtonClick { get; set; }
[Inject] public ICustomerChecklistService CustomerChecklistService { get; set; }
[Inject] public IStringLocalizer<SharedResources> Localizer { get; set; }
[CascadingParameter] public Routes App { get; set; }
private CustomerChecklistSettings _settings = new();
private PageResult<CustomerChecklistModel> _data = new();
private TelerikGrid<CustomerChecklistModel> _grid;
private async void OnPageReadAsync(GridReadEventArgs args)
{
var newOffset = args.Request.PageSize * (args.Request.Page - 1);
_settings.Offset = newOffset;
_settings.CurrentPageNumber = args.Request.Page;
var request = new CustomerChecklistRequestModel
{
Offset = _settings.Offset,
Limit = _settings.Limit,
ChecklistId = ChecklistId,
OrderBy = _settings.OrderBy,
IsCompleted = _settings.IsCompleted,
CustomerId = _settings.SelectedCustomerId
};
_data = await CustomerChecklistService.GetPageAsync(request);
args.Data = _data.Items;
args.Total = _data.Total;
StateHasChanged();
}
private void OnCustomerSelected(int customerId)
{
_settings.SelectedCustomerId = customerId;
_settings.CurrentPageNumber = 1;
_grid.Rebind();
}
private async Task OnEditCustomerChecklistButtonClickAsync(GridCommandEventArgs args)
{
var customerChecklist = args.Item as CustomerChecklistModel;
if (OnEditCustomerChecklistButtonClick.HasDelegate)
{
await OnEditCustomerChecklistButtonClick.InvokeAsync(customerChecklist);
}
}
private async Task OnNavigateToCustomerChecklistButtonClickAsync(GridCommandEventArgs args)
{
var customerChecklist = args.Item as CustomerChecklistModel;
if (OnNavigateToCustomerChecklistButtonClick.HasDelegate)
{
await OnNavigateToCustomerChecklistButtonClick.InvokeAsync(customerChecklist);
}
}
private sealed class CustomerChecklistSettings
{
public int Offset { get; set; }
public int Limit { get; set; } = 25;
public int CurrentPageNumber { get; set; } = 1;
public int? SelectedCustomerId { get; set; }
public bool? IsCompleted { get; set; }
public string OrderBy { get; set; } = "customer";
}
}
This is a screenshot of the UI I'm seeing. I'm expecting data to show up in the inner grid, data gets loaded correctly (when debugging I see data is available), however no records are shown.
Hello,
I use the scheduler component with grouping ressources on a timeline view.
When there is only one ressource for the second group, there is no text showing on the view.
It works with 2 ressources.
I try over your demo and i have the same result.
https://blazorrepl.telerik.com/GyYAmBGE22N2CcIQ27
When I use 2 ressources it works.
Thank you
I want to be able to limit the Scheduler to a minimum and/or maximum date.
---
ADMIN EDIT
---
This request could be revised in two separate tracks:
Please share your thoughts on how you'd expect it to be implemented.
Hello!
After update to 2.27 there is a bug to scheduler with number of days and refresh.
You can check your live demo also:
In a Scheduler with vertical grouping, I am dynamically changing the displayed resources during runtime. It looks like displaying a longer resource name breaks the rendering.
The Scheduler does not recalculate the cell width when changing the resource list.
---
ADMIN EDIT
---
Possible workarounds:
I am really looking to try to do something like this in Blazor: ASP.NET MVC Scheduler Hierarchical Grouping Demo | Telerik UI for ASP.NET MVC
In MVC, this feature is implemented with the dataParentValueField.
Current behavior:
When I click on an empty slot in the Scheduler the Start and End DateTimePickers are set to the default value of a DateTime object - 1/1/0001.
Expected/Desired behavior:
When I click on an empty slot in the Scheduler, the Start and End DateTimePickers should be set to the start and end date and time of the clicked slot.