I am using Kendo UI’s Scheduler component as a planning tool.
I am using the multi-week-view component to visualize the workitems of multiple people across multiple workplaces.
When I add/edit/remove event items, Kendo’s own FocusService will automatically attempt to focus on an item in the scheduler component. but it keeps focusing on the first event item in the scheduler.
How can I change this, so that it focuses on the event I just added or edited?
My Code:
planning.table.html
.....
<div
*ngIf="timeRegistrations$ | async as timeRegistrations"
#dropWrapper
dropTargetFilter=".drop-zone"
(onDrop)="onExternalDrop($event, scheduler)"
(onDragEnter)="onExternalDragEnter($event)"
(onDragLeave)="onExternalDragLeave($event)"
kendoDropTargetContainer
>
<kendo-scheduler
#scheduler
class="scheduler"
kendoSchedulerSlotSelectable
[resources]="resources"
[loading]="loading$ | async"
[kendoSchedulerBinding]="editService.events | async"
[selectable]="true"
[modelFields]="editService.fields"
[kendoSchedulerReactiveEditing]="createFormGroup"
[selectedDate]="selectedDate$ | async"
[eventHeight]="40"
[slotClass]="setSlotClass"
[eventClass]="setEventClass"
[eventStyles]="setEventStyles"
[group]="{ orientation: 'vertical', resources: ['Werkplekken'] }"
(save)="onSaveEvent($event)"
(remove)="onRemoveEvent($event)"
(create)="onCreateEvent($event)"
(cancel)="onCancelEvent($event)"
(dragEnd)="onDragEndEvent($event)"
(resizeEnd)="onResizeEndEvent($event)"
>
<kendo-scheduler-multi-week-view [numberOfWeeks]="1">
....
planning.table.ts
export class TablePlanningComponent implements OnInit {
@Input() resources: Resource[];
@Input() isExpanded: boolean = false;
@Output() sectionFilterEmitter = new EventEmitter<string>();
@Output() workplaceFilterEmitter = new EventEmitter<string>();
@Output() workplaceResetEmitter = new EventEmitter<string>();
@ViewChild('scheduler') scheduler: SchedulerComponent;
@ViewChild('dropWrapper', { read: DropTargetContainerDirective })
public dropTargetContainer: DropTargetContainerDirective;
timeRegistrations$ = this.planningFacade.timeRegistrations$;
selectedDate$ = this.planningFacade.selectedDate$;
loading$ = this.planningFacade.loading$;
_selectedViewIndex: number = 0;
formGroup: FormGroup;
weekDateList: Date[];
days = Days;
shifts: DailyShiftSetting[];
sections: Section[];
sectionFilter: string;
loadingMoreSections: boolean = false;
sectionPage: number = 1;
searchValue: FormControl = new FormControl(null, [
Validators.required,
Validators.minLength(3),
]);
searchValueChanged: boolean = false;
searchList: TimeRegistration[] = [];
searchIndex: number = 0;
searching: boolean = false;
constructor(
private formBuilder: FormBuilder,
private planningFacade: PlanningFacade,
private authFacade: AuthenticationFacade,
public editService: PlanningSchedulerEditService,
private sectionFacade: SectionFacade,
private employeeFacade: EmployeeFacade,
private modal: NzModalService,
private message: NzMessageService,
private dragAndDropService: PlanningDragAndDropService,
private translocoService: TranslocoService
) {
this.createFormGroup = this.createFormGroup.bind(this);
}
public ngOnInit(): void {
this.editService.read();
this.sectionFacade
.loadSections(this.sectionPage, 10)
.subscribe((result) => {
this.sections = result.items;
});
this.planningFacade.currentWeekDates().subscribe((weekList) => {
this.weekDateList = weekList;
});
this.authFacade.selectedOrganization$.subscribe((org) => {
this.shifts = org.dailyShiftSettings;
});
this.searchValue.valueChanges.subscribe(() => {
this.searchValueChanged = true;
this.searchList = [];
this.searchIndex = 0;
});
}
createFormGroup(args: CreateFormGroupArgs): FormGroup {
const dataItem = args.dataItem;
const day = getDay(dataItem.start);
const shift = this.shifts.find((x) => x.dayOfWeek == day);
let start = dataItem.start;
let end = dataItem.end;
if (args.isNew) {
start = setHours(start, shift.shiftStart);
end = setHours(start, shift.shiftEnd);
}
this.formGroup = this.formBuilder.group({
id: args.isNew ? null : dataItem.id,
start: [start, Validators.required],
end: [end, Validators.required],
breakMinutes: [
args.isNew ? shift.breakMinutes : dataItem.breakMinutes,
Validators.required,
],
employeeId: [
args.isNew ? null : dataItem.employeeId,
Validators.required,
],
workplaceId: [dataItem.workplaceId, Validators.required],
activityId: [
args.isNew ? null : dataItem.activityId,
Validators.required,
],
});
return this.formGroup;
}
onDragEndEvent({ dataItem, start, end, resources }) {
const values = {
start: start,
end: end,
workplaceId: resources.workplaceId,
};
this.editService.update(dataItem, values);
}
onResizeEndEvent(event) {
event.preventDefault();
if (
isEqual(event.dataItem.start, event.start) &&
isEqual(event.dataItem.end, event.end)
) {
return;
} else {
const timeRegistrations =
this.planningFacade.resizeCreateTimeRegistrations(
event.dataItem,
event.start,
event.end
);
timeRegistrations.forEach((timeRegistration) => {
this.editService.create(timeRegistration);
});
}
}
onCancelEvent(event: CancelEvent) {
this.checkCurrentEvent(event.formGroup);
}
onSaveEvent({ formGroup, isNew, dataItem }: SaveEvent) {
if (formGroup.valid) {
const formValue = formGroup.value;
if (isNew) {
this.editService.create(formValue);
} else {
this.editService.update(dataItem, formValue);
}
}
this.checkCurrentEvent(formGroup);
}
onCreateEvent($event: CreateEvent) {
jumpToPreviousScrollPosition();
}
onRemoveEvent(event) {
event.preventDefault();
this.modal.confirm({
nzTitle: this.translocoService.translate('planning.service.confirmation'),
nzClosable: false,
nzIconType: null,
nzOkText: this.translocoService.translate('yes'),
nzOkType: 'primary',
nzOnOk: () => this.deleteEvent(event),
nzCancelText: this.translocoService.translate('no'),
nzOnCancel: () => {},
});
}
deleteEvent({ dataItem }: RemoveEvent) {
this.editService.remove(dataItem);
}
.....
}
planning-edit.service.ts
constructor(
private planningFacade: PlanningFacade,
private modal: NzModalService,
private message: NzMessageService,
private translocoService: TranslocoService
) {
super(fields);
}
public read(forceFetch?: boolean): void {
if (this.data.length && !forceFetch) {
this.source.next(this.data);
return;
}
this.fetch().subscribe((data) => {
this.data = data.timeRegistrations;
this.source.next(this.data);
});
}
protected fetch(action = '', data?: any): Observable<any> {
return this.planningFacade.selectedDate$.pipe(
switchMap((date) => {
return this.planningFacade.loadTimeRegistrations(date);
})
);
}
protected save(
created: TimeRegistration[],
updated: TimeRegistration[],
deleted: TimeRegistration[]
): void {
const completed = [];
if (deleted.length) {
completed.push(
deleted.map((item) => {
const id: string = item.id;
return this.planningFacade.deleteTimeRegistration(id).pipe(
catchError((error) => {
return of(
this.message.warning(
this.translocoService.translate(
'planning.service.errorUnknown'
)
)
);
})
);
})
);
}
if (updated.length) {
completed.push(
updated.map((item) => {
const request: UpdateTimeRegistrationRequest = {
id: item.id,
employeeId: item.employeeId,
startDateTime: item.start.toISOString(),
endDateTime: item.end.toISOString(),
breakMinutes: item.breakMinutes,
activityId: item.activityId,
workplaceId: item.workplaceId,
};
return this.planningFacade
.updateTimeRegistration(item.id, request)
.pipe(
catchError((error) => {
return of(
this.message.warning(
this.translocoService.translate(
'planning.service.errorUnknown'
)
)
);
})
);
})
);
}
if (created.length) {
completed.push(
created.map((item) => {
const request: CreateTimeRegistrationRequest = {
employeeId: item.employeeId,
startDateTime: item.start.toISOString(),
endDateTime: item.end.toISOString(),
breakMinutes: item.breakMinutes,
activityId: item.activityId,
workplaceId: item.workplaceId,
};
return this.planningFacade.createTimeRegistration(request).pipe(
catchError((error) => {
return of(
this.message.warning(
this.translocoService.translate(
'planning.service.errorUnknown'
)
)
);
})
);
})
);
}
zip(...completed).subscribe(() => {
this.read();
this.planningFacade.selectedDate$.subscribe((date) => {
this.planningFacade.loadUnplannedEmployees(date).subscribe();
});
});
}
}
So I tried the focus methods of the Kendo Scheduler component. but these are inadequate, as it only allows to focus on the last ‘active’ event. ( which kendo determines is the first one it loads), or to change the focused event relative to the current active event. which is not calculable, as you don’t know position of you last added/edited event relative to the active event.