If you need to wait until after the DOM is available, then you should use a renderer.
The afterRender event fires after the DOM is ready. You can then use this to assign values, modify DOM.
First you will need a function in your lightning component's helper:
{
postRender : function(component){
//...do some stuff here...
}
{
Second you need to have to call this from the renderer:
({
afterRender: function(component, helper) {
this.superAfterRender();
helper.postRender(component);
}
})
Normally you don't want to use renderers for arbitrary assigning values as this is what the attribute binding is intended to accomplish. But in this case where you are dynamically assigning which fields will even be present based on object...this sounds like a good use case.
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
});
},
})
Best Answer
There are a few issues in your example:
init is not synchronized in any way with dynamic loading of javascript or rendering which is why
<ltng:require afterScriptsLoaded="{!c.yourAfterScriptLoadedHandler}">
exists (it handles both of those challenges by sequencing one or more script onload events and the rendering lifecycle of lightning).init handlers are called long before rendering happens which means that there is a timing issue there too.
S1/LEX has built in tab content caching (I believe it hold on to the last 5 tabs' content) and init is only called once in a component's lifecycle (right after creation)
manipulating DOM elements that are created on your behalf by the framework puts you at odds with the auto rerendering process and direct CSS modifications are likely to be unceremoniously overwritten. If you really need to do this it should be done in afterRender()