[SalesForce] Render fullcalendar on a lightning component

I have a lightning component that pulls data from Salesforce and adds them as events on a calendar created using fullcalendar. The component helper contains the method fetching the data from Server. The component controller first pulls the data from Server and then creates the fullCalendar. This is the general structure of the code:

    calendar.cmp
    --------------------
    <aura:component...>
            <ltng:require scripts="/resource/jQuery,/resource/FullCalendar/js/fullcalendar.min.js"  afterScriptsLoaded="{!c.afterScriptsLoaded}"/>
    </aura>
    calendarController.js
    ---------------------
    afterScriptsLoaded: function(cmp,evt,helper){
     var events = cmp.get("v.events");
     if(events == null)
      {
         helper.fetchEvents(cmp);
      }
      helper.createCalendar(cmp,events);
    }

     calendarHelper.js
     ---------------------
     fetchEvents: function(cmp){
        var action = cmp.get("c.fetchFromServer");
        action.setCallback(this, function(response){ 
            var state = response.getState();
            if(cmp.isValid() && state === "SUCCESS"){
                cmp.set("v.events",response.getReturnValue());
            }
        });
     },
     createCalendar: function(cmp,events){
        $('#calendar').fullCalendar({...});
     }

I added the component to a lightning tab for testing.
The first time I visit the tab, the component loads. But when I visit another tab and come back to this tab, the component is blank, even though the controller and helper methods are invoked in correct sequence and the data is pulled from the server too. This is because the calendar div is not visible yet when FullCalendar attempts to draw the calendar. I tried calling .fullCalendar('render') in rerender,afterRender method too, but that didn't help.
However, when I reload the tab, the calendar is rendered correctly.

How can I ensure the data is rendered every time the calendar is accessed?

Best Answer

Hi @prashanthkr based on my comment i tried to prototype this, and it works like charm during inital load/refresh in both SF1 and desktop.

Edit on 20-09-2017:

Fullcalendar works for the below js version with LockerService(v40) in place :

jQuery - v3.2.1

MomentJS - v2.18.1

FullCalendar - v3.1.0/v3.5.1(latest)

NOTE: Libraries should be included in the above order.

Here's my code:

fullCalendar.cmp

<aura:component controller="FullCalendarController" implements="force:appHostable">
    <ltng:require styles="{!$Resource.FullCalenderJs     + '/fullcalendar/fullcalendar.css'}"
          scripts="{!join(',',
                       $Resource.FullCalenderJs  + '/fullcalendar/lib/jquery.min.js',
                       $Resource.FullCalenderJs  + '/fullcalendar/lib/moment.min.js',
                       $Resource.FullCalenderJs  + '/fullcalendar/fullcalendar.js'
                      )}"
          afterScriptsLoaded="{!c.afterScriptsLoaded}" />
    <ltng:require styles="/resource/FullCalendar/FullCalendar/fullcalendar.css"/>
    <aura:attribute name="events" type="Object[]" />
    <div id="calendar"></div>
</aura:component>

fullCalendarController.js

({
    afterScriptsLoaded: function(cmp,evt,helper){
        var events = cmp.get("v.events");
        console.log(events);
        if(!events.length)
        {
            helper.fetchEvents(cmp);
        }
    },

})

fullCalendarHelper.js

({
    loadDataToCalendar :function(data){        
        $('#calendar').fullCalendar({
            header: {
                left: 'prev,next today',
                center: 'title',
                right: 'month,basicWeek,basicDay'
            },
            defaultDate: '2016-04-01',
            editable: true,
            eventLimit: true,
            events:data
        });
    },

    tranformToFullCalendarFormat : function(component,events) {
        var eventArr = [];
        for(var i = 0;i < events.length;i++){
            eventArr.push({
                'id':events[i].Id,
                'start':events[i].StartDateTime,
                'end':events[i].EndDateTime,
                'title':events[i].Subject
            });
        }
        return eventArr;
    },

    fetchEvents : function(component) {
        var action = component.get("c.getEvents"); 
        var self = this;
        action.setCallback(this, function(response) {
            var state = response.getState();
            if(component.isValid() && state === "SUCCESS"){
                var eventArr = self.tranformToFullCalendarFormat(component,response.getReturnValue());
                self.loadDataToCalendar(component,eventArr);
                component.set("v.events",eventArr);
            }
        });

        $A.enqueueAction(action); 
    }, 
})

Apex Controller:(FullCalendarController)

public class FullCalendarController {

    @AuraEnabled
    public static List<Event> getEvents(){
        return [SELECT AccountId,EndDateTime,Id,StartDateTime,Subject FROM Event];
    }
}

-- UPDATE --

Above solution work properly in Standalone app not in LEX on subsequent loads. After hours of inspecting DOM Element returned by jquery selector which was different compared to the first time load,jquery was not returning the proper DOM when the component descriptor is loaded from cache.

So Inorder to get the proper DOM,on every load,i replaced the jquery selector $('#calendar') to component.find('#calendar').getElement() and it actually returns the proper DOM Element,and the fullcalendar is getting rendered on subsequent loads in LEX.

fullCalendar.cmp should be like this to support above:

<aura:component controller="FullCalendarController" implements="force:appHostable">
    <ltng:require styles="{!$Resource.FullCalenderJs     + '/fullcalendar/fullcalendar.css'}"
          scripts="{!join(',',
                       $Resource.FullCalenderJs  + '/fullcalendar/lib/jquery.min.js',
                       $Resource.FullCalenderJs  + '/fullcalendar/lib/moment.min.js',
                       $Resource.FullCalenderJs  + '/fullcalendar/fullcalendar.js'
                      )}"
          afterScriptsLoaded="{!c.afterScriptsLoaded}" />
    <ltng:require styles="/resource/FullCalendar/FullCalendar/fullcalendar.css"/>
    <aura:attribute name="events" type="Object[]" />
    <div aura:id="calendar"></div>
</aura:component>

And loadDataToCalendar method should look like this:

({
 loadDataToCalendar :function(component,data){      
            var ele = component.find('calendar').getElement();
            $(ele).fullCalendar({
                header: {
                    left: 'prev,next today',
                    center: 'title',
                    right: 'month,basicWeek,basicDay'
                },
                defaultDate: '2016-04-01',
                editable: true,
                eventLimit: true,
                events:data
            });
        },
})
Related Topic