Reputation: 15245
I've setup a calendar using FullCalendar with Angular UI. It works fine, I can toggle categories of events nicely, but every time the eventSource is updated the calender view is set to the current date.
I've tried using the gotoDate
method and I can see that it works (it also works from the console), but almost immediately after the calender is reverted to the current date. As I'm new to AngularJS I've probably put the gotoDate
in the wrong place. But I'm clueless were to put it elsewhere.
I'm using a service that returns a bunch of event objects and pushes them into eventSources
, the ng-model of the calendar element. Nothing special, in the controller I have:
$scope.eventSources = [];
var promise = UserCalendarEvents.get(groupName);
promise.then(
function(events) {
$scope.eventSources.push(events);
$('#events-calendar').fullCalendar('gotoDate', 2012, 11);
},
function(reason) {
console.log('Error: ' + reason);
}
);
In this case events are fetched and $scope.eventSources
is populated. The calender view is then set to december 2012 and after that, almost instantly, the view swithes to current date. Is it some kind of watch of the ng-model that rerenders the fullcalender and if so how can I set the date of choice?
Update: I ended using joshkurz fix, but in a modified version that honors the selected view, ie if the user has selected basicWeek and changes source data the view shouldn't change to for example month view. That's what I need for my users.
function update() {
scope.calendar = elm.html('');
var view = scope.calendar.fullCalendar('getView');
var m;
var xtraOptions = {};
//calendar object exposed on scope
if(view){
var viewDate = new Date(view.start);
if(m !== 'Invalid Date'){
y = viewDate.getFullYear();
m = viewDate.getMonth();
d = viewDate.getDate();
if(!isNaN(y) && !isNaN(m) && !isNaN(d)){
xtraOptions = {
year: y,
month: m,
date: d
};
}
}
view = view.name; //setting the default view to be whatever the current view is. This can be overwritten.
}
/* If the calendar has options added then render them */
var expression,
options = { defaultView : view, eventSources: sources };
if (attrs.uiCalendar) {
expression = scope.$eval(attrs.uiCalendar);
// Override defaultView if is set in ui-calendar attribute - OK?
if (expression.defaultView) {
expression.defaultView = view;
}
} else {
expression = {};
}
angular.extend(options, uiConfig.uiCalendar, expression, xtraOptions);
scope.calendar.fullCalendar(options);
}
Upvotes: 2
Views: 8434
Reputation: 9710
Another way to solve the issue without changing the Angular-UI source is to declare the calendar like this:
<div ui-calendar="{viewDisplay:viewDisplayHandler,month:monthVal,year:yearVal}" ng-model="eventsArr"></div>
And to have a viewDisplayHandler
function in the scope that sets monthVal
and yearVal
to the appropriate values in order to have the date on the calendar set after the whole calendar recreation:
$scope.viewDisplayHandler = function(view) {
var viewStart = view.start;
$scope.yearVal = viewStart.getFullYear();
$scope.monthVal = viewStart.getMonth();
}
This is how i solved it before issuing the pull request on GitHub; it's not the optimal method i guess, but i have been using it in production for a while and it seems to be ok and does not require changing Angular-UI's code.
Upvotes: 0
Reputation: 1033
This is a bug with the calendar. You are the first one to say anything about it on StackOverflow. Kudos.
There are a couple of ways that this could be fixed. Its been proposed on github https://github.com/angular-ui/angular-ui/pull/520 that we do away with how the directive re-creates itself anytime the watch is fired, which would stop this behavior. I believe that if we can get this method to work in production then it will be the best solution.
Until then however the fix is to get the current month from a date object created from the view.start field. This month should be added to the options which are used to render the calendar.
Here is a snippet of what the new update function should look like inside of the calendar directive.
/* update the calendar with the correct options */
function update() {
scope.calendar = elm.html('');
var view = scope.calendar.fullCalendar('getView');
var m;
var xtraOptions = {};
//calendar object exposed on scope
if(view){
var d = new Date(view.start);
m = new Date(view.start);
if(m !== 'Invalid Date'){
m = m.getMonth();
if(!isNaN(m)){
xtraOptions = {
month: m
};
}
}
view = view.name; //setting the default view to be whatever the current view is. This can be overwritten.
}
// console.log(m)
/* If the calendar has options added then render them */
var expression,
options = {
defaultView : view,
eventSources: sources
};
if (attrs.exCalendar) {
expression = scope.$eval(attrs.exCalendar);
} else {
expression = {};
}
angular.extend(options, uiConfig.exCalendar, expression, xtraOptions);
scope.calendar.fullCalendar(options);
}
This has not been properly tested on the angular-ui CI server, but it works fine as I am using it in production currently.
Upvotes: 3
Reputation: 4592
With AngularUI wrapping FullCalendar in a directive for you, the calendar object can be accessed via $scope.calendar. "The AngularJS Way" is to avoid direct DOM manipulation in controllers.
In your particular case, you'd write this instead:
$scope.calendar.fullCalendar('gotoDate', 2012, 11);
AngularUI does have a watch on eventSource and event that calls an update function every time the length of either changes. You can view the source here:
https://github.com/angular-ui/angular-ui/blob/master/modules/directives/calendar/calendar.js
You can see that the calendar object at $scope.calendar gets recreated with a new set of events everytime the event model changes. This is why your date change isn't going through -- the event is being added, triggering the update, your date change goes in, the whole calendar is changed and your date change is lost.
Two (not the best) things pop up at me without changing AngularUI's code:
You can use AngularJS's $timeout service and wait a set time after events are loaded, the calendar is finished updating, then call your date change.
You can add a $watch on the scope that triggers your date change everytime the calendar object changes:
I created that example above. In the test() method, I'm just loading some fake data into events via a promise (they'll be added in March 2013) then changing the date to December 2012. I'm watching $scope.calendar and everytime it changes (an update is triggered in the directive) I resend the date command. You should be sent to December 2012 without even seeing the new events go in, but if you go back to March 2013, they should be there. I stuck the watch in another .then assuming you'll use some value that's returned to set the date dynamically.
Upvotes: 0