Hello,
I am using a Schedule component to render custom events to a calendar using the Timeline Month and Timeline Week views.
The items that I render to the calendar have a custom property named type. This is a string that can be either "Plan" or "Audit".
Currently, I allow drag and drop and resizing of events and do NOT allow multi-select. I also make use of the Quick Info modal when the user left-clicks on an item.
Now, I have some requirements to enable multi-select in certain circumstances and would like your help identifying what features are possible using this component and which ones I will not be able to implement.
Hi Justin,
Greetings from Syncfusion support.
Sample: https://stackblitz.com/edit/ej2-react-schedule-multi-selection-customization-sample?file=index.js
Q1: Can I enable multi-select but ONLY allow users to select items that have the custom type property set to "Plan"?
Q2: Can I programmatically detect there is at least 1 item selected from OUTSIDE the component?
We have achieved your requirement with help of the eventClick event of the Schedule as shown in the below code snippet.
[index.js]
|
export class TimelineGrouping extends SampleBase { constructor(props) { super(props); this.state = { selectedAppointments: [] }; }
onEventClick(args) { if (args && args.event.Type === 'Audit') { args.cancel = true; args.element.classList.remove('e-appointment-border'); document.activeElement.blur(); } else { args.element.classList.remove('e-unselected-app'); var listOfApps = this.scheduleObj.element.querySelectorAll('.e-appointment-border:not(.e-schedule-event-clone)'); if (listOfApps.length < 1) { this.deSelectionHandler(); } else { this.setState({ selectedAppointments: listOfApps }); this.highlightSelectedApps(); } } }
render() { return ( <div> {this.state.selectedAppointments.length > 0 && <button onClick={this.deSelectionHandler.bind(this)}>Deselect items</button>} </div> ); } } |
Q3: Can I programmatically de-select all items from outside the scheduler component?
Q4: Is it possible to add custom styles to selected and unselected items?
We have achieved your requirement with help of the button onClick event as shown in the below code snippet.
[index.js]
|
deSelectionHandler(args) { var listOfApps = this.scheduleObj.element.querySelectorAll('.e-appointment.e-appointment-border'); if (listOfApps.length > 0) { for (var i = 0; i < listOfApps.length; i++) { listOfApps[i].classList.remove('e-appointment-border'); } } var unSelectedApps = this.scheduleObj.element.querySelectorAll('.e-appointment.e-unselected-app'); if (unSelectedApps.length > 0) { for (var i = 0; i < unSelectedApps.length; i++) { unSelectedApps[i].classList.remove('e-unselected-app'); } } this.setState({ selectedAppointments: [] }); document.activeElement.blur(); } |
Q5: Is it possible to prevent users from using the drag and drop and resize functionality when they have multiple items selected?
We have prevented the drag and drop and resize action with help of the dragStart and resizeStart events of the Schedule as shown in the below code snippet.
[index.js]
|
preventAction() { return this.scheduleObj.element.querySelectorAll('.e-appointment-border:not(.e-schedule-event-clone)').length > 1; }
onDragStart(args) { args.cancel = this.preventAction(); }
onResizeStart(args) { args.cancel = this.preventAction(); } |
Kindly try the shared sample and let us know if you need any further assistance on this.
Regards,
Ravikumar Venkatesan
Hi,
In your examples you use a react state to hold data (such as the list of selected items).
I am using a React Functional Component and whenever I set the state, it causes the component to re-render. And when the component re-renders, the Schedule component automatically makes a new request to the server to refresh its data.
This is a critical issue for me as the load time for my events is slow and every time it re-renders the component, the selected items are reset.
How can I handle this functionality WITHOUT causing the Schedule to re-query the server every time I update the state?
Note: I am using a DataManager and a custom Adaptor, if that is important.
Thanks
Whenever I call refreshLayout on the Schedule component, all my item selections are lost. This happens also when resizing the window.
How can I ensure the items that are "selected" remain "selected" after layout changes? In the eventClick handler, I added this:
element.classList.add('selected-calendar-item');
I have a style defined that makes these items appear slightly different. (For testing purposes, this style is simply applying outline: 6px green solid.)
When I refresh the layout by resizing the window or calling refreshLayout manually, the items in the DOM must be removed and replaced, because my style disappears.
Instead of using STATE to hold the list of selected plans, I have done the following.
When an item is clicked and can be selected, I add a new class to it:
element.classList.add('selected-calendar-item');
Then, I gather all items that have this class:
const listOfApps = schedulerRef
?.element
.querySelectorAll('.selected-calendar-item');
Next, I extract the Element objects.
let selectedNodes: Element[] = [];
for (let i = 0, len = listOfApps.length; i < len; i++) {
selectedNodes.push(listOfApps[i]);
}
Afterward, I pull out the "data-id" attribute for each Element. This attr is set to "Appointment_###" by the Schedule component. I remove the "Appointment_" and extract only the id.
const planIds = selectedNodes
.map(el => Number(el.getAttribute("data-id")?.replace("Appointment_", "")))
.filter(x => !isNaN(x));
Finally, I use the scheduler ref's eventsData array to find the calendar events whose ids match the ones found above.
const selectedPlans: IAuditPlanCalendarItem[] = (schedulerRef
?.eventsData as IAuditPlanCalendarItem[])
.filter(evt => planIds.some(p => p === evt.id));
Now, with this array of items, I dispatch via an action to Redux instead of using State so that the Scheduler doesn't rerender and re-query the server.
Is there an easier way to go about doing this? Or is this the intended way?
Hi Justin,
Thanks for the update.
Sample: https://stackblitz.com/edit/ej2-react-schedule-multi-selection-without-state?file=index.js
Q1: How can I handle this functionality WITHOUT causing the Schedule to re-query the server every time I update the state?
We have used the state to show/hide the deselection button alone. We can show/hide the deselection button without the state as shown in the below code snippet.
[index.js]
|
export class TimelineGrouping extends SampleBase { onEventClick(args) { if (args && args.event.Type === 'Audit') { } else { if (listOfApps.length < 1) { } else { document.getElementById('deselect-btn').style.display = ''; } } }
deSelectionHandler(args) { document.getElementById('deselect-btn').style.display = 'none'; }
render() { return ( <div> <button id='deselect-btn' style={{ display: 'none' }} onClick={this.deSelectionHandler.bind(this)}>Deselect items</button> </div> ); } }
const root = createRoot(document.getElementById('sample')); root.render(<TimelineGrouping />); |
Q2: How can I ensure the items that are "selected" remain "selected" after layout changes?
When a browser layout is adjusted the Schedule will rerender its layout and appointments to adjust its layout based on the browser layout. So, the selection will be lost. You can highlight the selected appointments after layout adjustment with help of the eventRendered event of the Schedule as shown in the below code snippet. To clear the selected appointments you need to use the deselection button otherwise the list of selected appointments is not set empty. So, the appointments will be selected on each layout re-rendering.
[index.js]
|
listOfSelectedApps = [];
onEventClick(args) { if (args && args.event.Type === 'Audit') { args.cancel = true; args.element.classList.remove('e-appointment-border'); document.activeElement.blur(); } else { args.element.classList.remove('e-unselected-app'); var listOfApps = this.scheduleObj.element.querySelectorAll('.e-appointment-border:not(.e-schedule-event-clone)'); if (listOfApps.length < 1) { this.listOfSelectedApps = []; this.deSelectionHandler(); } else { document.getElementById('deselect-btn').style.display = ''; this.listOfSelectedApps.push(args.element.dataset.id.slice(12, args.element.dataset.id.length)); this.highlightSelectedApps(); } } }
onActionBegin(args) { if (['viewNavigate', 'dateNavigate'].indexOf(args.requestType) > -1) { this.listOfSelectedApps = []; } }
onEventRendered(args) { if (this.listOfSelectedApps.length > 0) { args.element.classList.add(this.listOfSelectedApps.indexOf(args.data.Id + '') > -1 ? 'e-appointment-border' : 'e-unselected-app'); } }
deSelectionHandler(args) { this.listOfSelectedApps = []; } |
Q3: Is there an easier way to go about doing this? Or is this the intended way?
You can use your way of implementation it looks fine or you can use how we have handled the selection in our shared sample without the state if that meets your requirements.
Kindly try the shared sample and let us know if you need any further assistance on this.
Regards,
Ravikumar Venkatesan