How to hide events in fullCalendar?
Asked Answered
B

2

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).

Bela answered 22/12, 2020 at 18:49 Comment(14)
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 pagePolymath
@Polymath 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.Bela
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.Polymath
@Polymath Yes, exist individual events for render, but not global ones. In v3 did exist eventAfterAllRender ( 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.Bela
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 :-).Polymath
Of course @ADyson, and have a merry christmas!Bela
why you are not using proper custom view for events with proper padding and margins?Prase
fullcalendar.io/docs/custom-view-with-jsPrase
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.Bela
yes you can design your custom View as a classic view UIPrase
@saqibkafeel I've tried, but without any luck. Maybe you can give an answer with the given codepen.Bela
ibb.co/jT4sCNr @Bela check the this image link is it your desire result or you want something else?Prase
@saqibkafeel No, the result should be like the second image I posted: i.sstatic.net/blrRH.png I.e. when the event is not rendered it has not to reserve the space for itselfBela
@Bela 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 increasePrase
P
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/[email protected]/main.min.css">
<script src="https://unpkg.com/[email protected]/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/[email protected]/main.min.css">
<script src="https://unpkg.com/[email protected]/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/[email protected]/main.min.css">
<script src="https://unpkg.com/[email protected]/main.min.js"></script>
<button id="toggle">toggle</button>
<div id='calendar'></div>
Phytography answered 26/12, 2020 at 23:24 Comment(6)
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).Bela
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.Bela
@Bela 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)Phytography
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.Bela
@Bela I haved added another example with a display toggle on clickPhytography
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.Bela
F
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;
}
Fat answered 1/5, 2023 at 20:59 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.