Skip to content
This repository was archived by the owner on May 25, 2019. It is now read-only.

Commit 71ea70d

Browse files
committed
Merge pull request #405 from joshkurz/calendar
Re-Wrote Calendar to Watch All Event Sources
2 parents bf20d88 + 0747a21 commit 71ea70d

File tree

2 files changed

+161
-102
lines changed

2 files changed

+161
-102
lines changed
Lines changed: 48 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -1,63 +1,65 @@
11
/*
22
* AngularJs Fullcalendar Wrapper for the JQuery FullCalendar
3-
* inspired by http://arshaw.com/fullcalendar/
3+
* API @ http://arshaw.com/fullcalendar/
44
*
5-
* Basic Angular Calendar Directive that takes in live events as the ng-model and watches that event array for changes, to update the view accordingly.
6-
* Can also take in an event url as a source object(s) and feed the events per view.
5+
* Angular Calendar Directive that takes in the [eventSources] nested array object as the ng-model and watches (eventSources.length + eventSources[i].length) for changes.
6+
* Can also take in multiple event urls as a source object(s) and feed the events per view.
7+
* The calendar will watch any eventSource array and update itself when a delta is created
8+
* An equalsTracker attrs has been added for use cases that would render the overall length tracker the same even though the events have changed to force updates.
79
*
810
*/
911

1012
angular.module('ui.directives').directive('uiCalendar',['ui.config', '$parse', function (uiConfig,$parse) {
11-
uiConfig.uiCalendar = uiConfig.uiCalendar || {};
12-
//returns the fullcalendar
13-
return {
13+
uiConfig.uiCalendar = uiConfig.uiCalendar || {};
14+
//returns calendar
15+
return {
1416
require: 'ngModel',
1517
restrict: 'A',
16-
scope: {
17-
events: "=ngModel"
18-
},
19-
link: function(scope, elm, $attrs) {
20-
var ngModel = $parse($attrs.ngModel);
21-
//update method that is called on load and whenever the events array is changed.
18+
link: function(scope, elm, attrs, $timeout) {
19+
var sources = scope.$eval(attrs.ngModel);
20+
var tracker = 0;
21+
/* returns the length of all source arrays plus the length of eventSource itself */
22+
var getSources = function () {
23+
tracker = 0;
24+
angular.forEach(sources,function(value,key){
25+
if(angular.isArray(value)){
26+
tracker += value.length;
27+
}
28+
});
29+
if(angular.isNumber(equalsTracker)){
30+
return tracker + sources.length + equalsTracker;
31+
}else{
32+
return tracker + sources.length;
33+
}
34+
};
35+
/* update the calendar with the correct options */
2236
function update() {
23-
//Default View Options
37+
//calendar object exposed on scope
38+
scope.calendar = elm.html('');
39+
var view = scope.calendar.fullCalendar('getView');
40+
if(view){
41+
view = view.name; //setting the default view to be whatever the current view is. This can be overwritten.
42+
}
43+
/* If the calendar has options added then render them */
2444
var expression,
2545
options = {
26-
header: {
27-
left: 'prev,next today',
28-
center: 'title',
29-
right: 'month,agendaWeek,agendaDay'
30-
},
31-
// add event name to title attribute on mouseover.
32-
eventMouseover: function(event, jsEvent, view) {
33-
if (view.name !== 'agendaDay') {
34-
$(jsEvent.target).attr('title', event.title);
35-
}
36-
},
37-
38-
// Calling the events from the scope through the ng-model binding attribute.
39-
events: scope.events
40-
};
41-
//if attrs have been entered to the directive, then create a relative expression.
42-
if ($attrs.uiCalendar){
43-
expression = scope.$eval($attrs.uiCalendar);
44-
}
45-
else{
46+
defaultView : view,
47+
eventSources: sources
48+
};
49+
if (attrs.uiCalendar) {
50+
expression = scope.$eval(attrs.uiCalendar);
51+
} else {
4652
expression = {};
47-
}
48-
//extend the options to suite the custom directive.
53+
}
4954
angular.extend(options, uiConfig.uiCalendar, expression);
50-
//call fullCalendar from an empty html tag, to keep angular happy.
51-
elm.html('').fullCalendar(options);
55+
scope.calendar.fullCalendar(options);
5256
}
53-
//on load update call.
5457
update();
55-
//watching the length of the array to create a more efficient update process.
56-
scope.$watch( 'events.length', function( newVal, oldVal )
57-
{
58-
//update the calendar on every change to events.length
59-
update();
60-
});
61-
}
58+
/* watches all eventSources */
59+
scope.$watch(getSources, function( newVal, oldVal )
60+
{
61+
update();
62+
});
63+
}
6264
};
63-
}]);
65+
}]);

modules/directives/calendar/test/calendarSpec.js

Lines changed: 113 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -18,38 +18,48 @@ describe('uiCalendar', function () {
1818

1919
// create an array of events, to pass into the directive.
2020
scope.events = [
21-
{
22-
title: 'All Day Event',
23-
start: new Date(y, m, 1),
24-
url: 'http://www.angularjs.org'},
25-
{
26-
title: 'Long Event',
27-
start: new Date(y, m, d - 5),
28-
end: new Date(y, m, d - 2)},
29-
{
30-
id: 999,
31-
title: 'Repeating Event',
32-
start: new Date(y, m, d - 3, 16, 0),
33-
allDay: false},
34-
{
35-
id: 999,
36-
title: 'Repeating Event',
37-
start: new Date(y, m, d + 4, 16, 0),
38-
allDay: true}
39-
]; //End of Events Array
40-
41-
scope.addChild = function() {
42-
scope.events.push({
21+
{title: 'All Day Event',start: new Date(y, m, 1),url: 'http://www.angularjs.org'},
22+
{title: 'Long Event',start: new Date(y, m, d - 5),end: new Date(y, m, d - 2)},
23+
{id: 999,title: 'Repeating Event',start: new Date(y, m, d - 3, 16, 0),allDay: false},
24+
{id: 999,title: 'Repeating Event',start: new Date(y, m, d + 4, 16, 0),allDay: true}];
25+
26+
// create an array of events, to pass into the directive.
27+
scope.events2 = [
28+
{title: 'All Day Event 2',start: new Date(y, m, 1),url: 'http://www.atlantacarlocksmith.com'},
29+
{title: 'Long Event 2',start: new Date(y, m, d - 5),end: new Date(y, m, d - 2)},
30+
{id: 998,title: 'Repeating Event 2',start: new Date(y, m, d - 3, 16, 0),allDay: false},
31+
{id: 998,title: 'Repeating Event 2',start: new Date(y, m, d + 4, 16, 0),allDay: true}];
32+
33+
// create an array of events, to pass into the directive.
34+
scope.events3 = [{title: 'All Day Event 3',start: new Date(y, m, 1),url: 'http://www.yoyoyo.com'}];
35+
36+
//event Sources array
37+
scope.eventSources = [scope.events,scope.events2]; //End of Events Array
38+
39+
scope.addSource = function(source) {
40+
scope.eventSources.push(source);
41+
};
42+
43+
scope.addChild = function(array) {
44+
array.push({
4345
title: 'Click for Google ' + scope.events.length,
4446
start: new Date(y, m, 28),
4547
end: new Date(y, m, 29),
4648
url: 'http://google.com/'
4749
});
4850
};
4951

50-
scope.remove = function(index) {
51-
scope.events.splice(index,1);
52-
};
52+
scope.remove = function(array,index) {
53+
array.splice(index,1);
54+
};
55+
56+
scope.uiConfig = {
57+
calendar:{
58+
height: 200,
59+
weekends: false,
60+
defaultView: 'month'
61+
}
62+
};
5363

5464
}));
5565

@@ -60,66 +70,113 @@ describe('uiCalendar', function () {
6070
describe('compiling this directive and checking for events inside the calendar', function () {
6171

6272

63-
//test the calendars events length to be 4
73+
/* test the calendar's events length */
6474
it('should excpect to load 4 events to scope', function () {
6575
spyOn($.fn, 'fullCalendar');
66-
$compile('<div id="uicalendar" ui-calendar="{height: 200, weekends: false}" ng-model="events"></div>')(scope);
67-
expect($.fn.fullCalendar.mostRecentCall.args[0].events.length).toBe(4);
76+
$compile('<div ui-calendar="uiConfig.calendar" ng-model="eventSources"></div>')(scope);
77+
expect($.fn.fullCalendar.mostRecentCall.args[0].eventSources[0].length).toBe(4);
6878
});
69-
//test to check the title of the first event.
79+
/* test to check the title of the first event. */
7080
it('should excpect to be All Day Event', function () {
7181
spyOn($.fn, 'fullCalendar');
72-
$compile('<div id="uicalendar" ui-calendar="{height: 200, weekends: false}" ng-model="events"></div>')(scope);
73-
expect($.fn.fullCalendar.mostRecentCall.args[0].events[0].title).toBe('All Day Event');
82+
$compile('<div ui-calendar="uiConfig.calendar" ng-model="eventSources"></div>')(scope);
83+
expect($.fn.fullCalendar.mostRecentCall.args[0].eventSources[0][0].title).toBe('All Day Event');
7484
});
75-
//test to make sure the event has a url assigned to it.
85+
/* test to make sure the event has a url assigned to it. */
7686
it('should expect the url to = http://www.angularjs.org', function () {
7787
spyOn($.fn, 'fullCalendar');
78-
$compile('<div id="uicalendar" ui-calendar="{height: 200, weekends: false}" ng-model="events"></div>')(scope);
79-
expect($.fn.fullCalendar.mostRecentCall.args[0].events[0].url).toBe('http://www.angularjs.org');
88+
$compile('<div ui-calendar="uiConfig.calendar" ng-model="eventSources"></div>')(scope);
89+
expect($.fn.fullCalendar.mostRecentCall.args[0].eventSources[0][0].url).toBe('http://www.angularjs.org');
90+
expect($.fn.fullCalendar.mostRecentCall.args[0].eventSources[1][0].url).toBe('http://www.atlantacarlocksmith.com');
8091
});
81-
//test the 3rd events' allDay field.
92+
/* test the 3rd events' allDay field. */
8293
it('should expect the fourth Events all Day field to equal true', function () {
8394
spyOn($.fn, 'fullCalendar');
84-
$compile('<div id="uicalendar" ui-calendar="{height: 200, weekends: false}" ng-model="events"></div>')(scope);
85-
expect($.fn.fullCalendar.mostRecentCall.args[0].events[3].allDay).toNotBe(false);
95+
$compile('<div ui-calendar="uiConfig.calendar" ng-model="eventSources"></div>')(scope);
96+
expect($.fn.fullCalendar.mostRecentCall.args[0].eventSources[0][3].allDay).toNotBe(false);
8697
});
87-
//Tests the height of the calendar.
98+
/* Tests the height of the calendar. */
8899
it('should expect the calendar attribute height to be 200', function () {
89100
spyOn($.fn, 'fullCalendar');
90-
$compile('<div id="uicalendar" ui-calendar="{height: 200, weekends: false}" ng-model="events"></div>')(scope);
91-
//console.log('hello ' + $.fn.fullCalendar.mostRecentCall.args[0]);
92-
expect($.fn.fullCalendar.mostRecentCall.args[0].height).toEqual(200);
93-
101+
$compile('<div ui-calendar="uiConfig.calendar" ng-model="eventSources"></div>')(scope);
102+
expect($.fn.fullCalendar.mostRecentCall.args[0].height).toEqual(200);
94103
});
95-
//Tests the weekends boolean of the calendar.
104+
/* Tests the weekends boolean of the calendar. */
96105
it('should expect the calendar attribute weekends to be false', function () {
97106
spyOn($.fn, 'fullCalendar');
98-
$compile('<div id="uicalendar" ui-calendar="{height: 200, weekends: false}" ng-model="events"></div>')(scope);
107+
$compile('<div ui-calendar="uiConfig.calendar" ng-model="eventSources"></div>')(scope);
99108
expect($.fn.fullCalendar.mostRecentCall.args[0].weekends).toEqual(false);
100109
});
101-
//Test to make sure that when an event is added to the calendar everything is updated with the new event.
110+
/* Test to make sure that when an event is added to the calendar everything is updated with the new event. */
102111
it('should expect the scopes events to increase by 2', function () {
103112
spyOn($.fn, 'fullCalendar');
104-
$compile('<div id="uicalendar" ui-calendar="{height: 200, weekends: false}" ng-model="events"></div>')(scope);
105-
expect($.fn.fullCalendar.mostRecentCall.args[0].events.length).toEqual(4);
106-
scope.addChild();
107-
scope.addChild();
108-
expect($.fn.fullCalendar.mostRecentCall.args[0].events.length).toEqual(6);
113+
$compile('<div ui-calendar="uiConfig.calendar" ng-model="eventSources"></div>')(scope);
114+
expect($.fn.fullCalendar.mostRecentCall.args[0].eventSources[0].length).toEqual(4);
115+
scope.addChild(scope.events);
116+
scope.addChild(scope.events);
117+
expect($.fn.fullCalendar.mostRecentCall.args[0].eventSources[0].length).toEqual(6);
109118
});
110-
//Test to make sure the calendar is updating itself on changes to events length.
119+
/* Test to make sure the calendar is updating itself on changes to events length. */
111120
it('should expect the calendar to update itself with new events', function () {
112121
spyOn($.fn, 'fullCalendar');
113-
$compile('<div id="uicalendar" ui-calendar="{height: 200, weekends: false}" ng-model="events"></div>')(scope);
114-
var clientEventsLength = $.fn.fullCalendar.mostRecentCall.args[0].events.length;
122+
$compile('<div ui-calendar="uiConfig.calendar" ng-model="eventSources"></div>')(scope);
123+
var clientEventsLength = $.fn.fullCalendar.mostRecentCall.args[0].eventSources[0].length;
115124
expect(clientEventsLength).toEqual(4);
116125
//remove an event from the scope.
117-
scope.remove(0);
126+
scope.remove(scope.events,0);
118127
//events should auto update inside the calendar.
119-
clientEventsLength = $.fn.fullCalendar.mostRecentCall.args[0].events.length;
128+
clientEventsLength = $.fn.fullCalendar.mostRecentCall.args[0].eventSources[0].length;
120129
expect(clientEventsLength).toEqual(3);
121130
});
131+
/* Test to make sure header options can be overwritten */
132+
it('should overwrite default header options', function () {
133+
spyOn($.fn, 'fullCalendar');
134+
scope.uiConfig2 = {
135+
calendar:{
136+
header: {center: 'title'}
137+
}
138+
};
139+
$compile('<div ui-calendar="uiConfig2.calendar" ng-model="eventSources"></div>')(scope);
140+
expect($.fn.fullCalendar.mostRecentCall.args[0].hasOwnProperty('header')).toEqual(true);
141+
var header = $.fn.fullCalendar.mostRecentCall.args[0].header;
142+
expect(header).toEqual({center: 'title'});
143+
});
144+
/* Test to see if calendar is watching all eventSources for changes. */
145+
it('should update the calendar if any eventSource array contains a delta', function () {
146+
spyOn($.fn, 'fullCalendar');
147+
$compile('<div ui-calendar="uiConfig2.calendar" ng-model="eventSources"></div>')(scope);
148+
var clientEventsLength = $.fn.fullCalendar.mostRecentCall.args[0].eventSources[0].length;
149+
var clientEventsLength2 = $.fn.fullCalendar.mostRecentCall.args[0].eventSources[1].length;
150+
expect(clientEventsLength).toEqual(4);
151+
expect(clientEventsLength2).toEqual(4);
152+
//remove an event from the scope.
153+
scope.remove(scope.events2,0);
154+
//events should auto update inside the calendar.
155+
clientEventsLength = $.fn.fullCalendar.mostRecentCall.args[0].eventSources[0].length;
156+
clientEventsLength2 = $.fn.fullCalendar.mostRecentCall.args[0].eventSources[1].length;
157+
expect(clientEventsLength).toEqual(4);
158+
expect(clientEventsLength2).toEqual(3);
159+
scope.remove(scope.events,0);
160+
clientEventsLength = $.fn.fullCalendar.mostRecentCall.args[0].eventSources[0].length;
161+
expect(clientEventsLength).toEqual(3);
162+
});
163+
/* Test to see if calendar is updating when a new eventSource is added. */
164+
it('should update the calendar if an eventSource is Added', function () {
165+
spyOn($.fn, 'fullCalendar');
166+
$compile('<div ui-calendar="uiConfig2.calendar" ng-model="eventSources"></div>')(scope);
167+
var clientEventSources = $.fn.fullCalendar.mostRecentCall.args[0].eventSources.length;
168+
expect(clientEventSources).toEqual(2);
169+
//add new source to calendar
170+
scope.addSource(scope.events3);
171+
//eventSources should auto update inside the calendar.
172+
clientEventSources = $.fn.fullCalendar.mostRecentCall.args[0].eventSources.length;
173+
expect(clientEventSources).toEqual(3);
174+
//go ahead and add some more events to the array and check those too.
175+
scope.addChild(scope.events3);
176+
var clientEventsLength = $.fn.fullCalendar.mostRecentCall.args[0].eventSources[2].length;
177+
expect(clientEventsLength).toEqual(2);
178+
});
122179

123180
});
124181

125-
});
182+
});

0 commit comments

Comments
 (0)