FullCallendar eventContent hook - how to tell if the event is single day or multi day? - fullcalendar

I am using the eventContent hook in FullCalendar to send back JSX so that my events are customized in the calendar. Works great. However, I'd like to format them differently based on if the events are single day or multi day. In the default view, single day events have a colored dot and multi day events have a colored bar.
The arg that gets passed into the eventContent hook has things like isStart, isEnd, etc., but nothing about isSingleDay or whatever.
Is there an attribute in the arg somewhere that has this info and I'm just not seeing it, or do I simply need to just check start and end and calculate myself if the event is single day or multi day?

For now, I ended up just using moment.js, since I was already using the moment plugin for FullCallendar. So, I'm just taking the start and end date values returned in the eventContent hook and using moment:
...
<FullCalendar
plugins={[
momentPlugin,
...
]}
eventContent={getEventDisplay}
...
/>
...
const getEventDisplay = (arg: EventContentArg) => {
const isSingleDay = isEventSingleDay(arg.event.start?.getTime() || 0, arg.event.end?.getTime() || 0);
...
}
...
const isEventSingleDay = (start: number, end: number): boolean => {
const startDate = moment(start);
const endDate = moment(end);
if (endDate.isSame(startDate, 'date')) return true;
return false;
}

Related

FullCalendar - selectOverlap prevents creation of allday events

Because selectOverlap function only passes through the event being overlapped, and not the selection, it is difficult to customise how to handle event creation.
In my case, we are working on a calendar/diary system, with background events showing the employee's shifts, and events showing their individual bookings.
At this point, other than the background events - absolutely no events should be able to overlap each other.
However... on top of that - we are then overlaying 'all day events' - which might be any number of things, but for examples sake, let's say they're 'staff birthdays' - therefore, you might have a couple of events today, but there will be an event in the all day section, showing someone's birthday.
I am checking for eventOverlap and doing some other checks on eventDrop and eventResize which handle different clashes, but these only work for existing events being moved or resized. I would like to do the same on event creation - which happens during the select. In order to disallow the select of spaces which already have events, I am using the example function from the selectOverlap documentation:
function(event) {
return event.rendering === 'background';
}
This works fantastically. However, if I try to create a new All Day event, it will 'overlap' any other events that exist on that day, and not pass this check.
I was hoping to be able to use the selection's object to check it for an allDay=true, but the function is only passed the existing event, and there is no way to check the selected section.
You can see a very simplified demo here:
https://codepen.io/anon/pen/NQrxOO
Try to create an allday event on the day which already has events.
Is there a better way to do this? I can completely remove the selectOverlap and do everything in the select callback instead, but I would need to essentially duplicate the overlap checks just to make this work, and I feel like that seems like overkill for something that should be relatively simple.
Is it possible to get not only the overlapped event object, but also the selection object when doing a selectOverlap function?
Current workaround is to remove the selectOverlap check, and instead do it myself within the select callback.
Below is a simplified version of a quick function I wrote to call when using select={this.handleEventCreate}:
class Diary extends React.Component {
//additional functions, state definitions, etc etc etc here.
//Define calendarRef as it will be needed in the function below
calendarRef = React.createRef();
handleEventCreate = (info) => {
// Get the Calendar API to allow unselection
let calendarApi = this.calendarRef.current.getApi();
// Get all Events
let checkEvents = calendarApi.getEvents();
// Loop through them
checkEvents.forEach(function(event){
// If it's not a background element, check whether the new element contains the existing, or vice versa.
if(event.rendering !== "inverse-background" &&
(
(event.start >= info.start && event.start <= info.end) ||
(event.end >= info.start && event.end <= info.end) ||
(info.start >= event.start && info.start <= event.end) ||
(info.end >= event.start && info.end <= event.end)
)
){
// It is an overlapping event, so we reject it.
return calendarApi.unselect();
}
});
alert('All good here to create the event.');
//extra event creation code to fire here.
}

Fullcalendar permit selectOverlap for one day

I'm using Fullcalendar and I need to allow event overlap for one day: the last still event day can overlap on the first new event day.
For resizing and drop I resolved in this way:
eventOverlap: function(stillEvent, movingEvent) {
var a = movingEvent.start.startOf('day');
var b = stillEvent.end.startOf('day');
if(a.unix() == b.unix()){
return true;
}
return false;
}
Now I need to do the same on selecting, according to documentation, I have to use selectOverlap option by define a function, but differently of eventOverlap, this function not have an argument that include the current selection period, then I don't know how to make the check.
Somebody can say me how to do so?
UPDATE 19/05/2017:
I opened an issue on github's project repository here

Front: ACF copy date from one field to another with jquery

I have three fields created via ACF with Datepicker: event start date, event end date and survey start date.
What I want to achieve is when I set date via datepicker on event start date, then this date is copied to event end date and survey start date.
I googled almost everything and no code is working. Actually, the nearest working solution is below:
acf.add_action('load', function( $el ){
var $field_start_date = $el.find('.acf-field-5800010541984');
var $field_end_date = $el.find('.acf-field-5800014941985 .input-alt');
$field_start_date.change(function() {
var $field_start_date_value = $('#acf-field_5800010541984').val();
$('#acf-field_5800014941985').datepicker( 'setDate', $field_start_date_value );
});
Value is copied to another field (event end date) – attribute value is changing – but copied value doesn't show on input.
BTW, $('#acf-field_5800014941985').datepicker('update'); doesn't work too.
Like Leeloo in The Fifth Element – "Pleeeeeeeeeeeeeese. Heeeeeeeeeeeeeelp"
Best regards,
Milosz!

how to check if subscribe is done loading all existing rows?

I want to load all the items on start without showing any message, but once after loaded. I want to capture any new row in subscriber and show it to the desktop notification.
The problem is, I'm not sure how to check if all the previous items are loaded and if the row is new item or is it from previous existing item.
this.items = this.af.database.list('notifications/'+this.uid+'/');
this.items.subscribe(list => {
list.forEach(row => {
// doing something here...
});
// once all the rows are finished loading, then any new row, show desktop notification message
});
I have user lodash for the minimal code.
// this varible holds the initial loaded keys
let loadedKeys = [];
this.items = this.af.database.list('notifications/'+this.uid+'/');
this.items.subscribe((list)=>{
// we skip it while initial load
if(!_.isEmpty(loadedKeys)){
// different is the new keys
let newKeys = _.difference(_.map(list, "$key"), loadedKeys);
if(!_.isEmpty(newKeys)){
// ... notification code here
}
}
loadedKeys = _.map(list, "$key");
});
The behave you are looking for is the default Subject approach in RxJS.
Check this reactiveX url to follow the marble diagram of Publish Subject (the equivalent for Subject in RxJS).
So you have two easy options:
1) manually index witch rows you want to display like #bash replied
2) create a Rx.Subject() and assign only the newest's rows to it. Then you subscribe to this subject in your app workflow.
The advantage of method 2 is when a new .subscribe occur, it will not retrieve previous data.
Edit: I wrote this codepen as a guide to implement your custom RxJS Subject. Hope it helps.
Assuming your rows have something unique to match with previous rows you can do the following:
// A Row item has a unique identifier id
interface Row {
id: number;
}
this.rows: Row[];
this.items$ = this.af.database.list(`notifications/${this.uid}/`).pipe(
tap(list => {
// if rows is not array, first time...
if(!Array.isArray(this.rows)) {
// first time nothing to do
return;
}
// returns true if some item from list is not found in this.rows
const foundNewRow = list.some(item => {
return ! this.rows.find(row => row.id === item.id);
});
if (foundNewRow) {
// call method to show desktop message here
}
}
);
I used a pipe and a tap operator (that you will have to import). If you subscribe to this.items$ the tap operator will do the work:
this.items$.subscribe((items => this.rows = items));
If you do not want to set this.rows when normally subscribing than you can also do this in the tap operator. But that would assume you only use it for checking difference between existing and new items.

Fullcalendar: getting "Va.time is undefined" when trying to assign a moment to event.end

I'm using FullCalendar and it is working fine.
I allow users to drag events, but sometimes I need to force the event to start on a specific date. For example, some events MUST start on a Monday, so if a user drags it to a different weekday, I'll force the event move to the previous Monday.
So, on the eventDrop callback, I have something like:
jQuery('#calendar').fullCalendar({
...
...
eventDrop: function(event, delta, revertFunc) {
if (/*must force new event start date*/) {
var duration = event.end.diff(event.start, 'd');
event.start = moment('2015-07-01');
event.end = moment('2015-07-01').add(duration, 'd');
}
}
})
Some explaining:
I must calculate the original duration, because when I change the
start date, Fullcalendar assumes the end date is the same and changes
the event duration accordingly. So it forces me to assign a new end
date (is there another way to do this?)
assigning a new date to event.start works fine
assigning a new date to event.end always returns:
TypeError: Va.time is undefined
Am I missing something, or maybe overcomplicating things?
Is the error a bug?
Thanks in advance for helping me on this!
Just modify the existing moment like this:
eventDrop: function (event) {
event.start.day(1); //Move the startdate to day 1 (Monday, 0 = Sunday)
event.end.day(1); //Also move the enddate to Monday
}
jsFiddle
I'm not sure what causes the error. It looks like it has to be something to do with setting a new momentjs object in either the event.start or event.end.

Resources