<!DOCTYPE html>
<html ng-app="calendarDemoApp">
<head>
<script src="//cdnjs.cloudflare.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/moment.js/2.8.2/moment.js"></script>
<script src="https://code.angularjs.org/1.2.24/angular.js"></script>
<link rel="stylesheet" href="//cdnjs.cloudflare.com/ajax/libs/fullcalendar/2.1.1/fullcalendar.min.css" />
<script src="//cdnjs.cloudflare.com/ajax/libs/fullcalendar/2.1.1/fullcalendar.min.js"></script>
<script src="//angular-ui.github.io/bootstrap/ui-bootstrap-tpls-0.9.0.js"></script>
<!--<script src="//angular-ui.github.io/ui-calendar/src/calendar.js"></script>-->
<script src="calendar.js"></script>
<link rel="stylesheet" href="style.css" />
<script src="script.js"></script>
</head>
<body>
<div calendar-view></div>
</body>
</html>
// Code goes here
angular
.module('calendarDemoApp', ['ui.calendar'])
.controller('calendarViewCtrl', function($scope, $timeout){
// For test events
var date = new Date();
var d = date.getDate();
var m = date.getMonth();
var y = date.getFullYear();
// Clear events and assign the static events source.
$scope.events = [];
$scope.staticEvents = [
{title: 'Static 1', start: new Date(y, m, 1), allDay: true},
{title: 'Static 2', start: new Date(y, m, 8), allDay: true},
{title: 'Static 3', start: new Date(y, m, d), allDay: true},
{title: 'Static 3', start: new Date(y, m, d), allDay: true},
{title: 'Static 3', start: new Date(y, m, d), allDay: true},
{title: 'Static 3', start: new Date(y, m, d), allDay: true},
{title: 'Static 3', start: new Date(y, m, d), allDay: true},
{title: 'Static 3', start: new Date(y, m, d), allDay: true},
{title: 'Static 3', start: new Date(y, m, d), allDay: true}
];
// Clear events via splice(0) and then push into events source.
$scope.getEventsEmptySplice = function () {
console.log('Clearing $scope.events via splice(0)');
// Clearing in this manner maintains the two-way data bind.
// This can be called over and over, with old events cleared,
// and new random events displayed. This no longer works
// if getEventsEmptyArray is ever called, due to two-way
// data bind being broken within that function.
$scope.events.splice(0);
// Get 3 random days, 1-28
/*var day1 = Math.floor(Math.random() * (28 - 1)) + 1;
var day2 = Math.floor(Math.random() * (28 - 1)) + 1;
var day3 = Math.floor(Math.random() * (28 - 1)) + 1;
*/
console.error("asdsadsa");
var d = [];
for(var i=0; i<240; i++){
d[i] = Math.floor(Math.random()*(28-1))+1;
console.log(i);
}
// Simulating an AJAX request with $timeout.
$timeout(function () {
// Create temp events array.
var newEvents = [];
for(var i=0;i<240; i++){
newEvents.push({title: 'random', start: new Date(y,m,d[i]), allDay:true});
}
/*var newEvents = [
{title: 'Random 1', start: new Date(y, m, day1), allDay: true},
{title: 'Random 2', start: new Date(y, m, day2), allDay: true},
{title: 'Random 3', start: new Date(y, m, day3), allDay: true}
];*/
// Push newEvents into events, one by one.
angular.forEach(newEvents, function (event) {
$scope.events.push(event);
});
console.log('New Events pushed');
}, 1);
};
// Clear events via empty array and then push into events source.
$scope.getEventsEmptyArray = function () {
console.log('Clearing $scope.events via assigning empty array');
// Clearing in this manner, assigns a new empty array
// to $scope.events, and breaks the two-way data bind.
// Once this is called, no update of $scope.events will
// change the calendar, because the events array in the controller
// scope is different from the events array in the calendar
// directive isolate scope.
$scope.events = [];
// Get 3 random days, 1-28
var day1 = Math.floor(Math.random() * (28 - 1)) + 1;
var day2 = Math.floor(Math.random() * (28 - 1)) + 1;
var day3 = Math.floor(Math.random() * (28 - 1)) + 1;
// Simulating an AJAX request with $timeout.
$timeout(function () {
// Create temp events array.
var newEvents = [
{title: 'Random 1', start: new Date(y, m, day1), allDay: true},
{title: 'Random 2', start: new Date(y, m, day2), allDay: true},
{title: 'Random 3', start: new Date(y, m, day3), allDay: true}
];
// Push newEvents into events, one by one.
angular.forEach(newEvents, function (event) {
$scope.events.push(event);
});
console.log('New Events pushed');
}, 100);
};
// Assign the 2 sources to $scope.eventSources for calendar.
$scope.eventSources = [$scope.staticEvents, $scope.events];
})
.directive('calendarView', function() {
function link($scope, $element, $attrs, $controller) {
}
return {
restrict: 'AE',
templateUrl: 'calendar-view.tpl.html',
controller: 'calendarViewCtrl',
scope: {},
link: link
}
});
/* Styles go here */
body {
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
font-size: 14px;
line-height: 1.42857143;
color: #333;
background-color: #fff;
}
/*
* AngularJs Fullcalendar Wrapper for the JQuery FullCalendar
* API @ http://arshaw.com/fullcalendar/
*
* Angular Calendar Directive that takes in the [eventSources] nested array object as the ng-model and watches it deeply changes.
* Can also take in multiple event urls as a source object(s) and feed the events per view.
* The calendar will watch any eventSource array and update itself when a change is made.
*
*/
angular.module('ui.calendar', [])
.constant('uiCalendarConfig', {})
.controller('uiCalendarCtrl', ['$scope', '$timeout', function($scope, $timeout){
var sourceSerialId = 1,
eventSerialId = 1,
sources = $scope.eventSources,
extraEventSignature = $scope.calendarWatchEvent ? $scope.calendarWatchEvent : angular.noop,
wrapFunctionWithScopeApply = function(functionToWrap){
var wrapper;
if (functionToWrap){
wrapper = function(){
// This happens outside of angular context so we need to wrap it in a timeout which has an implied apply.
// In this way the function will be safely executed on the next digest.
var args = arguments;
var _this = this;
$timeout(function(){
functionToWrap.apply(_this, args);
});
};
}
return wrapper;
};
this.eventsFingerprint = function(e) {
if (!e._id) {
e._id = eventSerialId++;
}
// This extracts all the information we need from the event. http://jsperf.com/angular-calendar-events-fingerprint/3
return "" + e._id + (e.id || '') + (e.title || '') + (e.url || '') + (+e.start || '') + (+e.end || '') +
(e.allDay || '') + (e.className || '') + extraEventSignature(e) || '';
};
this.sourcesFingerprint = function(source) {
return source.__id || (source.__id = sourceSerialId++);
};
this.allEvents = function() {
// return sources.flatten(); but we don't have flatten
var arraySources = [];
for (var i = 0, srcLen = sources.length; i < srcLen; i++) {
var source = sources[i];
if (angular.isArray(source)) {
// event source as array
arraySources.push(source);
} else if(angular.isObject(source) && angular.isArray(source.events)){
// event source as object, ie extended form
var extEvent = {};
for(var key in source){
if(key !== '_uiCalId' && key !== 'events'){
extEvent[key] = source[key];
}
}
for(var eI = 0;eI < source.events.length;eI++){
angular.extend(source.events[eI],extEvent);
}
arraySources.push(source.events);
}
}
return Array.prototype.concat.apply([], arraySources);
};
// Track changes in array by assigning id tokens to each element and watching the scope for changes in those tokens
// arguments:
// arraySource array of function that returns array of objects to watch
// tokenFn function(object) that returns the token for a given object
this.changeWatcher = function(arraySource, tokenFn) {
var self;
var getTokens = function() {
var array = angular.isFunction(arraySource) ? arraySource() : arraySource;
var result = [], token, el;
for (var i = 0, n = array.length; i < n; i++) {
el = array[i];
token = tokenFn(el);
map[token] = el;
result.push(token);
}
return result;
};
// returns elements in that are in a but not in b
// subtractAsSets([4, 5, 6], [4, 5, 7]) => [6]
var subtractAsSets = function(a, b) {
var result = [], inB = {}, i, n;
for (i = 0, n = b.length; i < n; i++) {
inB[b[i]] = true;
}
for (i = 0, n = a.length; i < n; i++) {
if (!inB[a[i]]) {
result.push(a[i]);
}
}
return result;
};
// Map objects to tokens and vice-versa
var map = {};
var applyChanges = function(newTokens, oldTokens) {
var i, n, el, token;
var replacedTokens = {};
var removedTokens = subtractAsSets(oldTokens, newTokens);
for (i = 0, n = removedTokens.length; i < n; i++) {
var removedToken = removedTokens[i];
el = map[removedToken];
delete map[removedToken];
var newToken = tokenFn(el);
// if the element wasn't removed but simply got a new token, its old token will be different from the current one
if (newToken === removedToken) {
self.onRemoved(el);
} else {
replacedTokens[newToken] = removedToken;
self.onChanged(el);
}
}
var addedTokens = subtractAsSets(newTokens, oldTokens);
for (i = 0, n = addedTokens.length; i < n; i++) {
token = addedTokens[i];
el = map[token];
if (!replacedTokens[token]) {
self.onAdded(el);
}
}
};
return self = {
subscribe: function(scope, onChanged) {
scope.$watch(getTokens, function(newTokens, oldTokens) {
if (!onChanged || onChanged(newTokens, oldTokens) !== false) {
applyChanges(newTokens, oldTokens);
}
}, true);
},
onAdded: angular.noop,
onChanged: angular.noop,
onRemoved: angular.noop
};
};
this.getFullCalendarConfig = function(calendarSettings, uiCalendarConfig){
var config = {};
angular.extend(config, uiCalendarConfig);
angular.extend(config, calendarSettings);
angular.forEach(config, function(value,key){
if (typeof value === 'function'){
config[key] = wrapFunctionWithScopeApply(config[key]);
}
});
return config;
};
}])
.directive('uiCalendar', ['uiCalendarConfig', '$locale', function(uiCalendarConfig, $locale) {
// Configure to use locale names by default
var tValues = function(data) {
// convert {0: "Jan", 1: "Feb", ...} to ["Jan", "Feb", ...]
var r, k;
r = [];
for (k in data) {
r[k] = data[k];
}
return r;
};
var dtf = $locale.DATETIME_FORMATS;
uiCalendarConfig = angular.extend({
monthNames: tValues(dtf.MONTH),
monthNamesShort: tValues(dtf.SHORTMONTH),
dayNames: tValues(dtf.DAY),
dayNamesShort: tValues(dtf.SHORTDAY)
}, uiCalendarConfig || {});
return {
restrict: 'A',
scope: {eventSources:'=ngModel',calendarWatchEvent: '&'},
controller: 'uiCalendarCtrl',
link: function(scope, elm, attrs, controller) {
var sources = scope.eventSources,
sourcesChanged = false,
eventSourcesWatcher = controller.changeWatcher(sources, controller.sourcesFingerprint),
eventsWatcher = controller.changeWatcher(controller.allEvents, controller.eventsFingerprint),
options = null;
function getOptions(){
var calendarSettings = attrs.uiCalendar ? scope.$parent.$eval(attrs.uiCalendar) : {},
fullCalendarConfig;
fullCalendarConfig = controller.getFullCalendarConfig(calendarSettings, uiCalendarConfig);
options = { eventSources: sources };
angular.extend(options, fullCalendarConfig);
var options2 = {};
for(var o in options){
if(o !== 'eventSources'){
options2[o] = options[o];
}
}
return JSON.stringify(options2);
}
scope.destroy = function(){
if(scope.calendar && scope.calendar.fullCalendar){
scope.calendar.fullCalendar('destroy');
}
if(attrs.calendar) {
scope.calendar = scope.$parent[attrs.calendar] = $(elm).html('');
} else {
scope.calendar = $(elm).html('');
}
};
scope.init = function(){
scope.calendar.fullCalendar(options);
};
eventSourcesWatcher.onAdded = function(source) {
scope.calendar.fullCalendar('addEventSource', source);
sourcesChanged = true;
};
eventSourcesWatcher.onRemoved = function(source) {
scope.calendar.fullCalendar('removeEventSource', source);
sourcesChanged = true;
};
eventsWatcher.onAdded = function(event) {
scope.calendar.fullCalendar('renderEvent', event);
};
eventsWatcher.onRemoved = function(event) {
scope.calendar.fullCalendar('removeEvents', function(e) {
return e._id === event._id;
});
};
eventsWatcher.onChanged = function(event) {
event._start = $.fullCalendar.moment(event.start);
event._end = $.fullCalendar.moment(event.end);
scope.calendar.fullCalendar('updateEvent', event);
};
eventSourcesWatcher.subscribe(scope);
eventsWatcher.subscribe(scope, function(newTokens, oldTokens) {
if (sourcesChanged === true) {
sourcesChanged = false;
// prevent incremental updates in this case
return false;
}
});
scope.$watch(getOptions, function(newO,oldO){
scope.destroy();
scope.init();
});
}
};
}]);
<h1>angular-ui-calendar source load test</h1>
<dl>
<dt><button type="button" ng-click="getEventsEmptySplice()">Get Events ( $scope.events.splice(0) )</button></dt>
<dd>Clearing in this manner maintains the two-way data bind.
This can be called over and over, with old events cleared,
and new random events displayed. This no longer works
if the other button is ever pushed, due to two-way
data bind being broken within that function.</dd>
<dt><button type="button" ng-click="getEventsEmptyArray()">Get Events ( $scope.events = [] )</button></dt>
<dd>Clearing in this manner, assigns a new empty array to
$scope.events, and breaks the two-way data bind.
Once this is called, no update of $scope.events will
change the calendar, because the events array in the
controller scope is now different from the events array
in the calendar directive isolate scope.</dd>
</dl>
<div ui-calendar="uiConfig.calendar" class="span8 calendar" ng-model="eventSources" calendar="myCalendar"></div>