sirjay
sirjay

Reputation: 1766

Fullcalendar.io: how to display one event per line in agendaWeek then mix all in one?

I use Fullcalendar.io v2

In my agendaWeek mod I have events and all of them are displayed on one line in day square. So, more events I have, then thiner event blocks.

How can I show one event per line? Like in month mod. And more events I have, then higher day block will me (height). Perhaps, it is hard to use functions like eventRender, because if you inspect .fs-event element (web developer tool), you will see event block uses position:absolute;top:300px;left:33%... so I don't know what to do.

enter image description here

I want something like this: enter image description here

Upvotes: 8

Views: 11040

Answers (3)

Hiddenkeg
Hiddenkeg

Reputation: 89

I also faced the same issue and while Alexander's answer is great, i had performance issues with it as it does lot of DOM manipulation. I have about 2000-3000 events per week and became unusable in browsers like Firefox, IE etc. Therefore adapting Alexander's answer and minimizing DOM manipulation came up with following solution.

variables

var itemsOnSlot = {};       // counter to save number of events in same time slot 
var maxItemsOnRow = {};     // counter to save max number of events in row

utilize eventRender and eventAfterAllRender callbacks

eventRender: function(event, element, view){
    // for each event, margin top and other css attributes are changed to stack events on top of each other
    if(!(event.start in itemsOnSlot)){          // if itemsOnSlot has no index with current event's start time
        itemsOnSlot[event.start] = 1;           // create index and set count to 1
        $(element).addClass('slot-attributes'); // add css to change the event style
    }else{                                      // if itemsOnSlot already has a index with current event's start time
        itemsOnSlot[event.start] += 1;          // increase counter by 1
        // change margin top to stack events on top of each other and add css to change the event style
        $(element).css('cssText','margin-top:'+(itemsOnSlot[event.start]*18)+'px !important;').addClass('slot-attributes');
    }
},

eventAfterAllRender: function(view) {

    // this loop is run to get the max number of events per row 
    for(var start in itemsOnSlot){          // for all the timeslots with events in them 
        var time = start.substr(16,8);      // extract required time format from index - eg. 14:00:00
        if(!(time in maxItemsOnRow)){       // if maxItemsOnRow has no index with current time
            maxItemsOnRow[time] = itemsOnSlot[start];   // create index and set count to current day's number of events in this time slot
        }else{                              // if maxItemsOnRow already has a index with current time
            if(itemsOnSlot[start] > maxItemsOnRow[time]){   // if current day's number of events in this time slot are greater than existing number
                maxItemsOnRow[time] = itemsOnSlot[start];   // replace current time's number of slots
            }
        }
    }

    // change height of each row using values from maxItemsOnRow
    $('div.fc-slats > table> tbody > tr[data-time]').each(function() {  // for all rows in calendar
        var time = $(this).attr('data-time');   // get time of each row
        if(time in maxItemsOnRow){              // if current row's time is in maxItemsOnRow
            $(this).css('cssText','height:'+(maxItemsOnRow[time]*18)+'px !important;'); // change the height of the row to contain max items in row
        }else{                                  // if current row's time is not in maxItemsOnRow (no events in current time slot)
            $(this).css('cssText','display: none !important;');    // hide timeslot
        }
    });

    // repaint the calendar with new dimensions
    $('#calendar').fullCalendar('option', 'aspectRatio', $('#calendar').fullCalendar('option', 'aspectRatio'));

    itemsOnSlot = {};     // reset variables
    maxItemsOnRow = {};     // reset variables
},

CSS

.slot-attributes {
    left: 4px !important;
    right: 3px !important;
    height: 15px !important;
    margin-right: 0 !important;
}

Upvotes: 0

Alexander Leyva Caro
Alexander Leyva Caro

Reputation: 1253

I was stuck with that problem too, it is very difficult because the plugin compose the calendar in an odd way, using some tables and locating the events with dynamically with a position absolute and varying the css top property.

However i found a generic solution that works very good. First i will show you the code, and them i will explain what exactly the code does.

I use the option eventAfterAllRender of the fullCalendar. This is an example working.enter image description here

I use moment for manage time and i assume the id of the fullCalendar html element is 'Calendar'.

eventAfterAllRender: function() {

    // define static values, use this values to vary the event item height
    var defaultItemHeight = 25;
    var defaultEventItemHeight = 18;
    // ...

    // find all rows and define a function to select one row with an specific time
    var rows = [];
    $('div.fc-slats > table > tbody > tr[data-time]').each(function() {
      rows.push($(this));
    });
    var rowIndex = 0;
    var getRowElement = function(time) {
      while (rowIndex < rows.length && moment(rows[rowIndex].attr('data-time'), ['HH:mm:ss']) <= time) {
        rowIndex++;
      }
      var selectedIndex = rowIndex - 1;
      return selectedIndex >= 0 ? rows[selectedIndex] : null;
    };

    // reorder events items position and increment row height when is necessary
    $('div.fc-content-col > div.fc-event-container').each(function() {  // for-each week column
      var accumulator = 0;
      var previousRowElement = null;

      $(this).find('> a.fc-time-grid-event.fc-v-event.fc-event.fc-start.fc-end').each(function() {  // for-each event on week column
        // select the current event time and its row
        var currentEventTime = moment($(this).find('> div.fc-content > div.fc-time').attr('data-full'), ['h:mm A']);
        var currentEventRowElement = getRowElement(currentEventTime);

        // the current row has to more than one item
        if (currentEventRowElement === previousRowElement) {
          accumulator++;

          // move down the event (with margin-top prop. IT HAS TO BE THAT PROPERTY TO AVOID CONFLICTS WITH FullCalendar BEHAVIOR)
          $(this).css('margin-top', '+=' + (accumulator * defaultItemHeight).toString() + 'px');

          // increse the heigth of current row if it overcome its current max-items
          var maxItemsOnRow = currentEventRowElement.attr('data-max-items') || 1;
          if (accumulator >= maxItemsOnRow) {
            currentEventRowElement.attr('data-max-items', accumulator + 1);
            currentEventRowElement.css('height', '+=' + defaultItemHeight.toString() + 'px');
          }
        } else {
          // reset count
          rowIndex = 0;
          accumulator = 0;
        }

        // set default styles for event item and update previosRow
        $(this).css('left', '0');
        $(this).css('right', '7px');
        $(this).css('height', defaultEventItemHeight.toString() + 'px');
        $(this).css('margin-right', '0');
        previousRowElement = currentEventRowElement;
      });
    });

    // this is used for re-paint the calendar
    $('#calendar').fullCalendar('option', 'aspectRatio', $('#calendar').fullCalendar('option', 'aspectRatio'));
  }

How the code works:

First i found all tr elements that are the rows of my calendar (note that they contains an attribute with its owns time).

Later I iterate for each column and get, for each one, its events items. Each event item is an anchor element with some inner child with the date as an attribute data-full.

From the event i peek what should be its row, and in that row if there are more than one item, then increase the position where the event item should be located. I use for that the margin-top property because this property is not used or readjust by the plugin (don't use top property).

In the row i set a data attribute to take the max amount of events that has any column of that row. With that, i can calculate if the row must be increase its height or not.

Well, this is basically what the codes does. If you have some question please do-it.

Upvotes: 4

suvroc
suvroc

Reputation: 3062

You can add a class to your events and try to customize this events with CSS

As an example you can use style

.test {
    width: 100%;
    height: auto;
    position: relative !important;
    left: 0% !important;
    margin-right: 0% !important;
}

and event like this:

{
    title: 'Lunch',
    start: '2014-06-09T10:30:00',
    className: 'test'
},

Look on this Fiddle if this is what you want to achieve

Also with a little bit workaround you can use eventAfterRender callback to adjust height of specific row. But this is not very safe solution and it requires some tuning:

eventAfterRender: function( event, element, view ) { 
    var row = $(".fc-slats tr:contains('"+ moment(event.start).format('ha') + "')");
    if (moment(event.start).format('mm') != '00')
    {
        row = row.next();
    }
    row.height(element.height()+row.height());
}

https://jsfiddle.net/m5uupf9x/3/

Upvotes: 2

Related Questions