Creating a Group of Identical Events in JS Scheduling Calendar with Drag-and-Drop

DHTMLX Scheduler is a comprehensive scheduling solution that covers a wide range of needs related to planning events. Suppose you haven’t found any features in our Scheduler documentation and you want to see them in your project in our Scheduler documentation. In that case, it is very likely that such functionalities can be implemented with a custom solution. This year, we’ll continue exploring the customization capabilities of our JavaScript scheduling component.

Today, you’ll learn how to enable the ability to easily add a range of similar events in the Week view of the calendar with drag-and-drop.

New Way to Schedule a Group of Events via UI

DHTMLX Scheduler is quite flexible when it comes to creating new events in a JavaScript calendar. End-users can plan various kinds of events, from basic one-time appointments to recurring activities based on various settings. But what if you need something in between? For instance, what if an end-user has a series of meetings (or other activities) in a similar format during the week? The use of the recurring events form is possible, but it may seem complex and time-consuming, especially when extra conditions are not required. Fortunately, the extensive API of our JavaScript scheduling component allows for the implementation of a custom solution like in the example below that we’ll consider in more detail.
adding groups of events with drag-and-dropCheck the sample >

As you can see, this solution makes creating a group of identical events much easier and faster. To be more specific, one drag operation helps create copies of events for each day within the required period. This customization includes not only the functional capability to create new events (appointments) with the same timeframe for multiple days a week but also some visual elements like rendering marked timespans for copies of the event.

From the coding perspective, this solution requires the use of the Scheduler API (plugins, methods, and events) as well as some custom functions (compareTime, getDatesBetween, getWeekdayNumbers, and timeFixer). These functions are used to transform available data into required formats.

Now we can proceed to the description of specific steps required for adding this functionality to your project.

Preparatory Step

You should start with a preparatory step, the result of which will be needed later. This includes enabling the plugin (limit) required for the visual part, setting optional configs, and creating variables.

scheduler.plugins({
   limit: true,
});

scheduler.config.time_step = 10;
scheduler.config.first_hour = 6;
scheduler.config.last_hour = 22;

let marked = null;
let start, end;
let backward;
let dates = [];
let daysToCreate = [];

Before we continue, it should also be noted that this customization is designed specifically for the Week view, while other views work in a regular mode. This limitation is enabled with the following condition:

if(scheduler.getState().mode == "week"){
Step 1

Now, you should use the onEventDrag event. It is called when a new event is created by a drag in the Week view of the calendar. It is active until the end of the drag operation. This event helps collect dates during the event drag operation and add them to the variables declared in the previous step:

scheduler.attachEvent("onEventDrag", function (id, mode, e){
    ...
    start = scheduler.getEvent(id).start_date;
    end = scheduler.getActionData(e).date;

Thus, you’ll get dates, when the copies of the initial event will be created.

Step 2

You create copies of the event using the custom getDatesBetween function that obtains a sequence of days between two dates and the getWeekdayNumbers function used to return numbers of these days.

These functions could be combined into one, but our approach seems to be clearer:

daysToCreate = getWeekdayNumbers(getDatesBetween(start, end));
daysToCreate = daysToCreate.filter(el => el != start.getDay())
Step 3

Also, you’ll need to use the custom compareTime function to check the direction of the event creation (forward or backward in time) by comparing hours and minutes:

backward = compareTime(start, end);

You will need this to correctly render placeholders for event copies.

It is the foundation of the functional part for this customization. The whole block of code is the following:

scheduler.attachEvent("onEventDrag", function (id, mode, e){

    start = scheduler.getEvent(id).start_date;
    end = scheduler.getActionData(e).date;
    backward = compareTime(start, end);

    daysToCreate = getWeekdayNumbers(getDatesBetween(start, end));
    daysToCreate = daysToCreate.filter(el => el != start.getDay())

}};
Step 4

At the end of the drag operation, use the onDragEvent event to determine start and end dates for creating events forward and backward in time:

scheduler.attachEvent("onDragEnd", function(id, mode, e){
    if(scheduler.getState().mode == "week"){
        // store dates for event copies
        if(backward != -1){
            start = scheduler.getActionData(e).date;
            end = scheduler.getEvent(id).end_date;
       
        } else {
            start = scheduler.getEvent(id).start_date;
            end = scheduler.getActionData(e).date;
        }
    }

});
Step 5

When the drag operation is finished, the parameters of the new event and its copies can be added via the lightbox. The onEventSave event will fire when the lightbox with data is saved.

scheduler.attachEvent("onEventSave",function(id,ev,is_new){

The parameters (start, end) of the getDatesBetween function updated in the onDragEnd event are used to fill up an array of dates for creating event copies.

In case, end-users change the event dates via the lightbox, you store new start and end parameters in the onEventSave event:

start = ev.start_date;
end = ev.end_date;

If the date array contains more than one date, you create a copy of the event for each day using the addEvent() method.

Using the custom timeFixer function, you can separately control the dates (minutes/hours) of event copies, so they match the initial event.

if(datesToCreate.length > 1){
        datesToCreate.forEach(el => {
        scheduler.addEvent({
             start_date: timeFixer(el).fixedStart,
             end_date: timeFixer(el).fixedEnd,
             text: ev.text
        })
})

After that, you clear the array of dates (datesToCreate):

datesToCreate = [];

The default way to create events should be blocked (it will also be replaced by a copy) and the lightbox is hidden manually.

// block default event creation
        scheduler.hideLightbox();
        return false;
    }
    return true;
})

Now, it works as intended functionally, but it’s not entirely clear where the event copies will be displayed.
adding a group of events via UICheck the sample >

This issue can be solved by adding the rendering of placeholders for event copies.

Step 6

Since the event copies need to be rendered dynamically during the drag process, they are taken from the onEventDrag event.

The placeholder is rendered with the markedTimespan() method. You’ll also need a couple of variables (zonesStartTime, zonesEndTime). These variables will store formatted time for the markedTimespan() method.

To get the formatted time, you’ll have to take the the event time in the common format (hours/minutes) and convert minutes to percentages of an hour using the custom convertMinutesToPercentage() method.

if(backward != -1){
    start = scheduler.getEvent(id).end_date;
    zonesEndTime = `${scheduler.getEvent(id).end_date.getHours()}.${convertMinutesToPercentage(scheduler.getEvent(id).end_date.getMinutes())}`;
    zonesStartTime = `${end.getHours()}.${convertMinutesToPercentage(end.getMinutes())}`;
} else {
    zonesStartTime = `${start.getHours()}.${convertMinutesToPercentage(start.getMinutes())}`;
    zonesEndTime = `${end.getHours()}.${convertMinutesToPercentage(end.getMinutes())}`;
}

Now you’ve got days to render the placeholder (daysToCreate) and time in the required format (zonesStartTime and zonesEndTime).

Here is the way to dynamically render placeholders during the event creation:

if(mode == "new-size"){
// Each time delete old placeholders
    scheduler.unmarkTimespan(marked);
// And render new placeholders for creating events
    if(+start < +end){
        marked = scheduler.markTimespan({
            html: `<div class="marked_placeholder">${scheduler.templates.event_date(start)+" - "+
            scheduler.templates.event_date(end)}
            </br> New Event<div>`,
            days: daysToCreate,
            zones:[zonesStartTime*60,zonesEndTime*60],
            css: "highlighted_timespan"
        });
    }
    if(+start > +end){
        marked = scheduler.markTimespan({
            html: `<div class="marked_placeholder">${scheduler.templates.event_date(end)+" - "+
            scheduler.templates.event_date(start)}
            </br> New Event<div>`,
            days: daysToCreate,
            zones:[zonesStartTime*60,zonesEndTime*60],
            css: "highlighted_timespan"
        });
    }
    }
});

One last thing to mention, all markTimespan should be deleted after the event and its copies. It is done right after the lightbox closure.

scheduler.attachEvent("onAfterLightbox", function (){
    // remove marked timespan after closing the lightbox
    scheduler.unmarkTimespan(marked);  
});
Click to see the final code
scheduler.attachEvent("onEventDrag", function (id, mode, e){
    if(scheduler.getState().mode == "week"){
        let zonesStartTime, zonesEndTime;
        // store dates for marked timespans
        start = scheduler.getEvent(id).start_date;
        end = scheduler.getActionData(e).date;
        backward = compareTime(start, end);
        // get hour/minute part for `zones` calculation *backward creation*
        if(backward != -1){
            start = scheduler.getEvent(id).end_date;
            zonesEndTime = `${scheduler.getEvent(id).end_date.getHours()}.${convertMinutesToPercentage(scheduler.getEvent(id).end_date.getMinutes())}`;
            zonesStartTime = `${end.getHours()}.${convertMinutesToPercentage(end.getMinutes())}`;
        } else {
            // get hour/minute part for `zones` calculation normal creation
            zonesStartTime = `${start.getHours()}.${convertMinutesToPercentage(start.getMinutes())}`;
            zonesEndTime = `${end.getHours()}.${convertMinutesToPercentage(end.getMinutes())}`;
        }
        daysToCreate = getWeekdayNumbers(getDatesBetween(start, end));
        daysToCreate = daysToCreate.filter(el => el != start.getDay())

    if(mode == "new-size"){
        scheduler.unmarkTimespan(marked);
        if(+start < +end){
            marked = scheduler.markTimespan({
                html: `<div class="marked_placeholder">${scheduler.templates.event_date(start)+" - "+
                scheduler.templates.event_date(end)}
                </br> New Event<div>`,
                days: daysToCreate,
                zones:[zonesStartTime*60,zonesEndTime*60],
                css: "highlighted_timespan"
            });
        }
        if(+start > +end){
            marked = scheduler.markTimespan({
                html: `<div class="marked_placeholder">${scheduler.templates.event_date(end)+" - "+
                scheduler.templates.event_date(start)}
                </br> New Event<div>`,
                days: daysToCreate,
                zones:[zonesStartTime*60,zonesEndTime*60],
                css: "highlighted_timespan"
            });
        }
    }
    }
});

Following the instructions above, you can add a convenient way for creating groups of events in the Week view with drag-and-drop like in our sample.

Wrapping Up

In this blog post, we’ve reviewed a useful customization that can contribute to more convenient event management and enhanced usability of a JavaScript scheduling solution in a web project. We reviewed just one example of implementing this kind of custom functionality, which can be modified even further and adopted for specific use-case scenarios. The rich and well-documented API of DHTMLX Scheduler certainly helps to cover most of your scheduling needs, while we will continue sharing with you solutions in DHTMLX tutorials to address more specific requirements.

Related Materials

Advance your web development with DHTMLX

Gantt chart
Event calendar
Diagram library
30+ other JS components