7

Using the latest version of fullCalendar (5.3.2) I want to hide some events that correspond to resources I don't want to show now in a given view. The standard way to do this is using an eventClassNames function to check for it and add a "hidden" class. Something like this:

eventClassNames: function(arg) {
   my_class = "";
   if (arg.view.type != 'resourceTimeGridDay') {
      if (arg.event.extendedProps.real_rc != "1") {
         my_class = 'hidden';
      }
   }
   return my_class;
}

using a simple CSS:

.fc-event.hidden {
  display: none;
}

This works fine, but has a problem when there is an overlap between a hidden event and a showed one. For instance in this case:

events: [
  {
    title: 'Resource 1',
    real_rc: '1',
    start: '2020-12-22 16:00',
    end: '2020-12-22 17:00'
  },
  {
    title: 'Resource 2',
    real_rc: '2',
    start: '2020-12-22 15:00',
    end: '2020-12-22 17:00'
  }
]

Only event with real_rc == 1 should be displayed, and in fact it is right, but the space used by the hidden event is reserved as you can see in this image:

Wrong result

If the event with the real_rc: 2 is ommited in the event list the result is the expected:

Expected result

I've used the Chrome DevTools to try to figure out what's happening and I think the problem is that the 'hidden' class is not set on the "outermost event element" as the fullCalendar states, but to a inner one:

enter image description here

(first DIV is the first event and as you can see the hidden class is set but not to the DIV, but to the a tag)

Here is a codepen to test it.

IMHO this is a fullCalendar bug, but now I have a problem and I need a solution. My options are:

  1. Use a CSS selector for the parent <-- IMPOSSIBLE: it doesn't exist (yet)
  2. Do (1) using jquery <-- I DON'T KNOW HOW? I know I need to execute something like $(".fc-event.hidden").parent().remove() when the events are loaded and showed but since v3 nothing like this exists UPDATE and even if it exist, with the current v5 removing the DOM element doesn't resize the other event boxes.
  3. Try to repair the code of the library <-- PROBLEMATIC: I don't want to worry about the patch if the next version of the library comes without a solution
  4. Filter events on load <-- SLOW: I use a callback function to load events through Ajax and I can do a fast filtering there, but in this case, I'll lose performance since I'll have to refetch events every time I need to show events with real_rc != 1
  5. Custom views <-- I DON'T WANT TO REINVENT THE WHEEL. As suggested by @saqibkafeel in a comment custom views can be used to create a new view, but I like current views and don't really need a new one, just the default views working as expected.

Is there a way to circumvent this problem without creating a new one? (I feel that the easiest option is to find a hook that allows me do the option number 2, but I have spent all the day and I haven't found anything).

Ivan
  • 14,692
  • 17
  • 59
  • 96
  • There's nothing stopping you using jQuery if you want to. jQuery can still be used to manipulate the HTML elements within the calendar. Just because fullCalendar stopped using it internally doesn't stop you adding it to your page – ADyson Dec 23 '20 at 08:36
  • @ADyson the problem with jQuery is that AFAIK there are no hooks or callbacks where I can add the code and assures that the events are loaded and rendered. There was a callback in v3, but it was removed. – Ivan Dec 23 '20 at 08:44
  • Which hooks are you talking about exactly? Some things have changed between the versions, but as far as I know very little, if anything, has actually been removed. There are still event render hooks, for example. – ADyson Dec 23 '20 at 09:52
  • @ADyson Yes, exist individual events for render, but not global ones. In v3 did exist `eventAfterAllRender` ( https://fullcalendar.io/docs/v3/eventAfterAllRender ). With this one I can got a reliable point where I can clear undesired events, but in v5 the only place is the `eventDidMount` but this is called only once, so if the engine renders again (i.e. click on change view) the events will be rendered again and `eventDidMount` won't be called. – Ivan Dec 23 '20 at 10:50
  • 1
    This is an interesting one. I need to look more closely at it, but don't have time right now...maybe in a few days when xmas is finished. Comment here though if you make any progress before that, then I don't waste time :-). – ADyson Dec 24 '20 at 10:07
  • 1
    Of course @ADyson, and have a merry christmas! – Ivan Dec 24 '20 at 10:09
  • why you are not using proper custom view for events with proper padding and margins? – saqib kafeel Dec 26 '20 at 16:32
  • https://fullcalendar.io/docs/custom-view-with-js – saqib kafeel Dec 26 '20 at 16:33
  • Thanks @saqibkafeel this is another option. I'm not using customviews because I just need the classic views but working as expected. I will investigate if this can solve the problem. – Ivan Dec 26 '20 at 16:42
  • yes you can design your custom View as a classic view UI – saqib kafeel Dec 26 '20 at 16:44
  • @saqibkafeel I've tried, but without any luck. Maybe you can give an answer with the given codepen. – Ivan Dec 27 '20 at 17:56
  • https://ibb.co/jT4sCNr @Ivan check the this image link is it your desire result or you want something else? – saqib kafeel Dec 27 '20 at 18:51
  • @saqibkafeel No, the result should be like the second image I posted: https://i.stack.imgur.com/blrRH.png I.e. when the event is not rendered it has not to reserve the space for itself – Ivan Dec 28 '20 at 17:54
  • 1
    @Ivan I already fix the issue using eventDidmout if you can see in my answer, otherwise provide me proper codePed code with event loading performance issue i will also debug on it, thanks and i think the bounty points should increase – saqib kafeel Dec 29 '20 at 04:31

2 Answers2

5

As you already noticed, CSS and JS cannot help here because the events are placed using position:absolute so removing one event (even completely from the DOM) won't affect the display of the other ones. The only way is to remove the event form the calender before rendring.

So remove the event instead of adding a class:

eventClassNames: function(arg) { /* you can also use "eventDidMount" */
       if (arg.view.type != 'resourceTimeGridDay') {
          if (arg.event.extendedProps.real_rc != "1") {
             arg.event.remove(); /* HERE */
          }
       }
    },

Full code:

document.addEventListener('DOMContentLoaded', function() {
  var calendarEl = document.getElementById('calendar');

  var calendar = new FullCalendar.Calendar(calendarEl, {
    initialView: 'timeGridWeek',
    initialDate: '2020-12-22',
    headerToolbar: {
      left: 'prev,next today',
      center: 'title',
      right: 'dayGridMonth,timeGridWeek,timeGridDay'
    },
    height: 'auto',

    eventClassNames: function(arg) {
       if (arg.view.type != 'resourceTimeGridDay') {
          if (arg.event.extendedProps.real_rc != "1") {
             arg.event.remove();
          }
       }
    },

    events: [
      {
        title: 'Test Resource 1',
        real_rc: '1',
        start: '2020-12-22 13:00',
        end: '2020-12-22 14:00'
      },
      {
        title: 'Also resource 1',
        real_rc: '1',
        start: '2020-12-22 13:30',
        end: '2020-12-22 14:30'
      },
      {
        title: 'Resource 1',
        real_rc: '1',
        start: '2020-12-22 16:00',
        end: '2020-12-22 17:00'
      },
      {
        title: 'Resource 2',
        real_rc: '2',
        start: '2020-12-22 15:00',
        end: '2020-12-22 17:00'
      }
    ]
  });

  calendar.render();
});
html, body {
  margin: 0;
  padding: 0;
  font-family: Arial, Helvetica Neue, Helvetica, sans-serif;
  font-size: 14px;
}

#calendar {
  max-width: 1100px;
  margin: 40px auto;
}
<link rel="stylesheet" href="https://unpkg.com/fullcalendar@5.1.0/main.min.css">
<script src="https://unpkg.com/fullcalendar@5.1.0/main.min.js"></script>
<div id='calendar'></div>

Another idea is to control the display of the event like below:

document.addEventListener('DOMContentLoaded', function() {
  var calendarEl = document.getElementById('calendar');

  var calendar = new FullCalendar.Calendar(calendarEl, {
    initialView: 'timeGridWeek',
    initialDate: '2020-12-22',
    headerToolbar: {
      left: 'prev,next today',
      center: 'title',
      right: 'dayGridMonth,timeGridWeek,timeGridDay'
    },
    height: 'auto',

    eventDidMount: function(arg) {
       if (arg.view.type != 'resourceTimeGridDay') {
          if (arg.event.extendedProps.real_rc != "1") {
             arg.event.setProp( 'display', 'none' );
          }
       }
    },

    events: [
      {
        title: 'Test Resource 1',
        real_rc: '1',
        start: '2020-12-22 13:00',
        end: '2020-12-22 14:00'
      },
      {
        title: 'Also resource 1',
        real_rc: '1',
        start: '2020-12-22 13:30',
        end: '2020-12-22 14:30'
      },
      {
        title: 'Resource 1',
        real_rc: '1',
        start: '2020-12-22 16:00',
        end: '2020-12-22 17:00'
      },
      {
        title: 'Resource 2',
        real_rc: '2',
        start: '2020-12-22 15:00',
        end: '2020-12-22 17:00'
      }
    ]
  });

  calendar.render();
});
html, body {
  margin: 0;
  padding: 0;
  font-family: Arial, Helvetica Neue, Helvetica, sans-serif;
  font-size: 14px;
}

#calendar {
  max-width: 1100px;
  margin: 40px auto;
}
<link rel="stylesheet" href="https://unpkg.com/fullcalendar@5.1.0/main.min.css">
<script src="https://unpkg.com/fullcalendar@5.1.0/main.min.js"></script>
<div id='calendar'></div>

An interactive demo where you can toggle display:

var rc = "1";

document.addEventListener('DOMContentLoaded', function() {
  var calendarEl = document.getElementById('calendar');

  var calendar = new FullCalendar.Calendar(calendarEl, {
    initialView: 'timeGridWeek',
    initialDate: '2020-12-22',
    headerToolbar: {
      left: 'prev,next today',
      center: 'title',
      right: 'dayGridMonth,timeGridWeek,timeGridDay'
    },
    height: 'auto',

    eventDidMount: function(arg) {
       if (arg.view.type != 'resourceTimeGridDay') {
          if (arg.event.extendedProps.real_rc != rc) {
             arg.event.setProp( 'display', 'none' );
          } 
       }
    },
    viewDidMount: function(arg) {
       var es = calendar.getEvents();
       for(var i=0;i<es.length;i++)
        es[i].setProp( 'display', 'auto' )
    },

    events: [
      {
        title: 'Test Resource 1',
        real_rc: '1',
        start: '2020-12-22 13:00',
        end: '2020-12-22 14:00'
      },
      {
        title: 'Also resource 1',
        real_rc: '1',
        start: '2020-12-22 13:30',
        end: '2020-12-22 14:30'
      },
      {
        title: 'Resource 1',
        real_rc: '1',
        start: '2020-12-22 16:00',
        end: '2020-12-22 17:00'
      },
      {
        title: 'Resource 2',
        real_rc: '2',
        start: '2020-12-22 15:00',
        end: '2020-12-22 17:00'
      }
    ]
  });

  calendar.render();
  document.querySelector("#toggle").addEventListener('click',function() {
    if (rc=="1") rc="2"; else rc = "1";
    /* trigger view change */
    calendar.changeView('dayGridMonth');
    calendar.changeView('timeGridWeek');
  });
});
html, body {
  margin: 0;
  padding: 0;
  font-family: Arial, Helvetica Neue, Helvetica, sans-serif;
  font-size: 14px;
}

#calendar {
  max-width: 1100px;
  margin: 40px auto;
}
<link rel="stylesheet" href="https://unpkg.com/fullcalendar@5.1.0/main.min.css">
<script src="https://unpkg.com/fullcalendar@5.1.0/main.min.js"></script>
<button id="toggle">toggle</button>
<div id='calendar'></div>
Temani Afif
  • 245,468
  • 26
  • 309
  • 415
  • Thanks, but this approach has a problem as I mentioned in the point number 4: you are deleting the events so you need to reload them if there is a change view. This is problematic for 2 reasons: first because AFAIK v5 has no hook for changeView and second (more important) because you need an extra AJAX call every time (and this is quite time consuming because I'm fetching thousands of events). – Ivan Dec 28 '20 at 18:42
  • Sorry, I've found a hook for change view: viewDidMount. With this hook your solution is feasible. I have to do an AJAX call, but at least it will work. – Ivan Dec 28 '20 at 18:49
  • @Ivan the first snippet is deleting but the second one is only hidding. With display the event still exist and you can still update later I guess (will try to update with an example) – Temani Afif Dec 28 '20 at 19:09
  • Yes, the second only hides, but `eventDidMount` is only called once when the events are loaded, so if `resourceTimeGridDay` is the view when you load events then they are not hidden and if not they are hidden always. The expected behaviour is like when you use `eventClassNames` that it's called with each event render, so with each view change the event gets one or other class. – Ivan Dec 29 '20 at 08:48
  • 1
    @Ivan I haved added another example with a display toggle on click – Temani Afif Dec 29 '20 at 09:55
  • Thanks! That approach by enumerating the events and modifying its properties is new to me and it works well as a workaround until this fullcalendar bug is patched. – Ivan Dec 29 '20 at 11:59
1

You can solve this using CSS' has selector

I solved this in react using a renderer function that returns an event component with a class name like 'my-event hidden' and added a style like the following:

a.fc-event:has(.my-event.hidden) {
  position: absolute !important;
  top: -9999px !important;
  left: -9999px !important;
}
erik w
  • 11
  • 2