Free cookie consent management tool by TermsFeed Policy Generator

source: branches/WebJobManager/HeuristicLab.Clients.Hive.WebJobManager/Scripts/GlobalJS/ui-calendar.js @ 15802

Last change on this file since 15802 was 13754, checked in by jlodewyc, 9 years ago

#2582 User management done, start resource calendar

File size: 12.9 KB
Line 
1/*
2*  AngularJs Fullcalendar Wrapper for the JQuery FullCalendar
3*  API @ http://arshaw.com/fullcalendar/
4*
5*  Angular Calendar Directive that takes in the [eventSources] nested array object as the ng-model and watches it deeply 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 change is made.
8*
9*/
10
11angular.module('ui.calendar', [])
12  .constant('uiCalendarConfig', {calendars: {}})
13  .controller('uiCalendarCtrl', ['$scope',
14                                 '$locale', function(
15                                  $scope,
16                                  $locale){
17
18      var sources = $scope.eventSources,
19          extraEventSignature = $scope.calendarWatchEvent ? $scope.calendarWatchEvent : angular.noop,
20
21          wrapFunctionWithScopeApply = function(functionToWrap){
22              return function(){
23                  // This may happen outside of angular context, so create one if outside.
24
25                  if ($scope.$root.$$phase) {
26                      return functionToWrap.apply(this, arguments);
27                  } else {
28                      var args = arguments;
29                      var self = this;
30                      return $scope.$root.$apply(function(){
31                          return functionToWrap.apply(self, args);
32                      });
33                  }
34              };
35          };
36
37      var eventSerialId = 1;
38      // @return {String} fingerprint of the event object and its properties
39      this.eventFingerprint = function(e) {
40        if (!e._id) {
41          e._id = eventSerialId++;
42        }
43       
44        var extraSignature = extraEventSignature({event: e}) || '';
45        var start = moment.isMoment(e.start) ? e.start.unix() : (e.start ? moment(e.start).unix() : '');
46        var end =   moment.isMoment(e.end)   ? e.end.unix()   : (e.end   ? moment(e.end).unix()   : '');
47       
48        // This extracts all the information we need from the event. http://jsperf.com/angular-calendar-events-fingerprint/3
49        return "" + e._id + (e.id || '') + (e.title || '') + (e.url || '') + start + end +
50          (e.allDay || '') + (e.className || '') + extraSignature;
51      };
52
53      var sourceSerialId = 1, sourceEventsSerialId = 1;
54      // @return {String} fingerprint of the source object and its events array
55      this.sourceFingerprint = function(source) {
56          var fp = '' + (source.__id || (source.__id = sourceSerialId++)),
57              events = angular.isObject(source) && source.events;
58          if (events) {
59              fp = fp + '-' + (events.__id || (events.__id = sourceEventsSerialId++));
60          }
61          return fp;
62      };
63
64      // @return {Array} all events from all sources
65      this.allEvents = function() {
66        // do sources.map(&:events).flatten(), but we don't have flatten
67        var arraySources = [];
68        for (var i = 0, srcLen = sources.length; i < srcLen; i++) {
69          var source = sources[i];
70          if (angular.isArray(source)) {
71            // event source as array
72            arraySources.push(source);
73          } else if(angular.isObject(source) && angular.isArray(source.events)){
74            // event source as object, ie extended form
75            var extEvent = {};
76            for(var key in source){
77              if(key !== '_id' && key !== 'events'){
78                 extEvent[key] = source[key];
79              }
80            }
81            for(var eI = 0;eI < source.events.length;eI++){
82              angular.extend(source.events[eI],extEvent);
83            }
84            arraySources.push(source.events);
85          }
86        }
87        return Array.prototype.concat.apply([], arraySources);
88      };
89
90      // Track changes in array of objects by assigning id tokens to each element and watching the scope for changes in the tokens
91      // @param {Array|Function} arraySource array of objects to watch
92      // @param tokenFn {Function} that returns the token for a given object
93      // @return {Object}
94      //  subscribe: function(scope, function(newTokens, oldTokens))
95      //    called when source has changed. return false to prevent individual callbacks from firing
96      //  onAdded/Removed/Changed:
97      //    when set to a callback, called each item where a respective change is detected
98      this.changeWatcher = function(arraySource, tokenFn) {
99        var self;
100        var getTokens = function() {
101          var array = angular.isFunction(arraySource) ? arraySource() : arraySource;
102          var result = [], token, el;
103          for (var i = 0, n = array.length; i < n; i++) {
104            el = array[i];
105            token = tokenFn(el);
106            map[token] = el;
107            result.push(token);
108          }
109          return result;
110        };
111
112        // @param {Array} a
113        // @param {Array} b
114        // @return {Array} elements in that are in a but not in b
115        // @example
116        //  subtractAsSets([6, 100, 4, 5], [4, 5, 7]) // [6, 100]
117        var subtractAsSets = function(a, b) {
118          var result = [], inB = {}, i, n;
119          for (i = 0, n = b.length; i < n; i++) {
120            inB[b[i]] = true;
121          }
122          for (i = 0, n = a.length; i < n; i++) {
123            if (!inB[a[i]]) {
124              result.push(a[i]);
125            }
126          }
127          return result;
128        };
129
130        // Map objects to tokens and vice-versa
131        var map = {};
132
133        // Compare newTokens to oldTokens and call onAdded, onRemoved, and onChanged handlers for each affected event respectively.
134        var applyChanges = function(newTokens, oldTokens) {
135          var i, n, el, token;
136          var replacedTokens = {};
137          var removedTokens = subtractAsSets(oldTokens, newTokens);
138          for (i = 0, n = removedTokens.length; i < n; i++) {
139            var removedToken = removedTokens[i];
140            el = map[removedToken];
141            delete map[removedToken];
142            var newToken = tokenFn(el);
143            // if the element wasn't removed but simply got a new token, its old token will be different from the current one
144            if (newToken === removedToken) {
145              self.onRemoved(el);
146            } else {
147              replacedTokens[newToken] = removedToken;
148              self.onChanged(el);
149            }
150          }
151
152          var addedTokens = subtractAsSets(newTokens, oldTokens);
153          for (i = 0, n = addedTokens.length; i < n; i++) {
154            token = addedTokens[i];
155            el = map[token];
156            if (!replacedTokens[token]) {
157              self.onAdded(el);
158            }
159          }
160        };
161        return self = {
162          subscribe: function(scope, onArrayChanged) {
163            scope.$watch(getTokens, function(newTokens, oldTokens) {
164              var notify = !(onArrayChanged && onArrayChanged(newTokens, oldTokens) === false);
165              if (notify) {
166                applyChanges(newTokens, oldTokens);
167              }
168            }, true);
169          },
170          onAdded: angular.noop,
171          onChanged: angular.noop,
172          onRemoved: angular.noop
173        };
174      };
175
176      this.getFullCalendarConfig = function(calendarSettings, uiCalendarConfig){
177          var config = {};
178
179          angular.extend(config, uiCalendarConfig);
180          angular.extend(config, calendarSettings);
181
182          angular.forEach(config, function(value,key){
183            if (typeof value === 'function'){
184              config[key] = wrapFunctionWithScopeApply(config[key]);
185            }
186          });
187
188          return config;
189      };
190
191    this.getLocaleConfig = function(fullCalendarConfig) {
192      if (!fullCalendarConfig.lang || fullCalendarConfig.useNgLocale) {
193        // Configure to use locale names by default
194        var tValues = function(data) {
195          // convert {0: "Jan", 1: "Feb", ...} to ["Jan", "Feb", ...]
196          var r, k;
197          r = [];
198          for (k in data) {
199            r[k] = data[k];
200          }
201          return r;
202        };
203        var dtf = $locale.DATETIME_FORMATS;
204        return {
205          monthNames: tValues(dtf.MONTH),
206          monthNamesShort: tValues(dtf.SHORTMONTH),
207          dayNames: tValues(dtf.DAY),
208          dayNamesShort: tValues(dtf.SHORTDAY)
209        };
210      }
211      return {};
212    };
213  }])
214  .directive('uiCalendar', ['uiCalendarConfig', function(uiCalendarConfig) {
215    return {
216      restrict: 'A',
217      scope: {eventSources:'=ngModel',calendarWatchEvent: '&'},
218      controller: 'uiCalendarCtrl',
219      link: function(scope, elm, attrs, controller) {
220
221        var sources = scope.eventSources,
222            sourcesChanged = false,
223            calendar,
224            eventSourcesWatcher = controller.changeWatcher(sources, controller.sourceFingerprint),
225            eventsWatcher = controller.changeWatcher(controller.allEvents, controller.eventFingerprint),
226            options = null;
227
228        function getOptions(){
229          var calendarSettings = attrs.uiCalendar ? scope.$parent.$eval(attrs.uiCalendar) : {},
230              fullCalendarConfig;
231
232          fullCalendarConfig = controller.getFullCalendarConfig(calendarSettings, uiCalendarConfig);
233
234          var localeFullCalendarConfig = controller.getLocaleConfig(fullCalendarConfig);
235          angular.extend(localeFullCalendarConfig, fullCalendarConfig);
236          options = { eventSources: sources };
237          angular.extend(options, localeFullCalendarConfig);
238          //remove calendars from options
239          options.calendars = null;
240
241          var options2 = {};
242          for(var o in options){
243            if(o !== 'eventSources'){
244              options2[o] = options[o];
245            }
246          }
247          return JSON.stringify(options2);
248        }
249
250        scope.destroyCalendar = function(){
251          if(calendar && calendar.fullCalendar){
252            calendar.fullCalendar('destroy');
253          }
254          if(attrs.calendar) {
255            calendar = uiCalendarConfig.calendars[attrs.calendar] = $(elm).html('');
256          } else {
257            calendar = $(elm).html('');
258          }
259        };
260
261        scope.initCalendar = function(){
262          if (!calendar) {
263            calendar = angular.element(elm).html('');
264          }
265          calendar.fullCalendar(options);
266          if(attrs.calendar) {
267            uiCalendarConfig.calendars[attrs.calendar] = calendar;
268          }         
269        };
270        scope.$on('$destroy', function() {
271          scope.destroyCalendar();
272        });
273
274        eventSourcesWatcher.onAdded = function(source) {
275          if (calendar && calendar.fullCalendar) {
276            calendar.fullCalendar(options);
277            if (attrs.calendar) {
278                uiCalendarConfig.calendars[attrs.calendar] = calendar;
279            }
280            calendar.fullCalendar('addEventSource', source);
281            sourcesChanged = true;
282          }
283        };
284
285        eventSourcesWatcher.onRemoved = function(source) {
286          if (calendar && calendar.fullCalendar) {
287            calendar.fullCalendar('removeEventSource', source);
288            sourcesChanged = true;
289          }
290        };
291
292        eventSourcesWatcher.onChanged = function() {
293          if (calendar && calendar.fullCalendar) {
294            calendar.fullCalendar('refetchEvents');
295            sourcesChanged = true;
296          }
297
298        };
299
300        eventsWatcher.onAdded = function(event) {
301          if (calendar && calendar.fullCalendar) {
302            calendar.fullCalendar('renderEvent', event, (event.stick ? true : false));
303          }
304        };
305
306        eventsWatcher.onRemoved = function(event) {
307          if (calendar && calendar.fullCalendar) {
308            calendar.fullCalendar('removeEvents', event._id);
309          }
310        };
311
312        eventsWatcher.onChanged = function(event) {
313          if (calendar && calendar.fullCalendar) {
314            var clientEvents = calendar.fullCalendar('clientEvents', event._id);
315            for (var i = 0; i < clientEvents.length; i++) {
316              var clientEvent = clientEvents[i];
317              clientEvent = angular.extend(clientEvent, event);
318              calendar.fullCalendar('updateEvent', clientEvent);
319            }
320          }
321        };
322
323        eventSourcesWatcher.subscribe(scope);
324        eventsWatcher.subscribe(scope, function() {
325          if (sourcesChanged === true) {
326            sourcesChanged = false;
327            // return false to prevent onAdded/Removed/Changed handlers from firing in this case
328            return false;
329          }
330        });
331
332        scope.$watch(getOptions, function(newValue, oldValue) {
333          if(newValue !== oldValue) {
334            scope.destroyCalendar();
335            scope.initCalendar();
336          } else if((newValue && angular.isUndefined(calendar))) {
337            scope.initCalendar();
338          }
339        });
340      }
341    };
342}]);
Note: See TracBrowser for help on using the repository browser.