EventManager.js 9.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414
  1. fc.sourceNormalizers = [];
  2. fc.sourceFetchers = [];
  3. var ajaxDefaults = {
  4. dataType: 'json',
  5. cache: false
  6. };
  7. var eventGUID = 1;
  8. function EventManager(options, _sources) {
  9. var t = this;
  10. // exports
  11. t.isFetchNeeded = isFetchNeeded;
  12. t.fetchEvents = fetchEvents;
  13. t.addEventSource = addEventSource;
  14. t.removeEventSource = removeEventSource;
  15. t.updateEvent = updateEvent;
  16. t.renderEvent = renderEvent;
  17. t.removeEvents = removeEvents;
  18. t.clientEvents = clientEvents;
  19. t.normalizeEvent = normalizeEvent;
  20. // imports
  21. var trigger = t.trigger;
  22. var getView = t.getView;
  23. var reportEvents = t.reportEvents;
  24. // locals
  25. var stickySource = { events: [] };
  26. var sources = [ stickySource ];
  27. var rangeStart, rangeEnd;
  28. var currentFetchID = 0;
  29. var pendingSourceCnt = 0;
  30. var loadingLevel = 0;
  31. var cache = [];
  32. for (var i=0; i<_sources.length; i++) {
  33. _addEventSource(_sources[i]);
  34. }
  35. /* Fetching
  36. -----------------------------------------------------------------------------*/
  37. function isFetchNeeded(start, end) {
  38. return !rangeStart || start < rangeStart || end > rangeEnd;
  39. }
  40. function fetchEvents(start, end) {
  41. rangeStart = start;
  42. rangeEnd = end;
  43. cache = [];
  44. var fetchID = ++currentFetchID;
  45. var len = sources.length;
  46. pendingSourceCnt = len;
  47. for (var i=0; i<len; i++) {
  48. fetchEventSource(sources[i], fetchID);
  49. }
  50. }
  51. function fetchEventSource(source, fetchID) {
  52. _fetchEventSource(source, function(events) {
  53. if (fetchID == currentFetchID) {
  54. if (events) {
  55. if (options.eventDataTransform) {
  56. events = $.map(events, options.eventDataTransform);
  57. }
  58. if (source.eventDataTransform) {
  59. events = $.map(events, source.eventDataTransform);
  60. }
  61. // TODO: this technique is not ideal for static array event sources.
  62. // For arrays, we'll want to process all events right in the beginning, then never again.
  63. for (var i=0; i<events.length; i++) {
  64. events[i].source = source;
  65. normalizeEvent(events[i]);
  66. }
  67. cache = cache.concat(events);
  68. }
  69. pendingSourceCnt--;
  70. if (!pendingSourceCnt) {
  71. reportEvents(cache);
  72. }
  73. }
  74. });
  75. }
  76. function _fetchEventSource(source, callback) {
  77. var i;
  78. var fetchers = fc.sourceFetchers;
  79. var res;
  80. for (i=0; i<fetchers.length; i++) {
  81. res = fetchers[i](source, rangeStart, rangeEnd, callback);
  82. if (res === true) {
  83. // the fetcher is in charge. made its own async request
  84. return;
  85. }
  86. else if (typeof res == 'object') {
  87. // the fetcher returned a new source. process it
  88. _fetchEventSource(res, callback);
  89. return;
  90. }
  91. }
  92. var events = source.events;
  93. if (events) {
  94. if ($.isFunction(events)) {
  95. pushLoading();
  96. events(cloneDate(rangeStart), cloneDate(rangeEnd), function(events) {
  97. callback(events);
  98. popLoading();
  99. });
  100. }
  101. else if ($.isArray(events)) {
  102. callback(events);
  103. }
  104. else {
  105. callback();
  106. }
  107. }else{
  108. var url = source.url;
  109. if (url) {
  110. var success = source.success;
  111. var error = source.error;
  112. var complete = source.complete;
  113. // retrieve any outbound GET/POST $.ajax data from the options
  114. var customData;
  115. if ($.isFunction(source.data)) {
  116. // supplied as a function that returns a key/value object
  117. customData = source.data();
  118. }
  119. else {
  120. // supplied as a straight key/value object
  121. customData = source.data;
  122. }
  123. // use a copy of the custom data so we can modify the parameters
  124. // and not affect the passed-in object.
  125. var data = $.extend({}, customData || {});
  126. var startParam = firstDefined(source.startParam, options.startParam);
  127. var endParam = firstDefined(source.endParam, options.endParam);
  128. if (startParam) {
  129. data[startParam] = Math.round(+rangeStart / 1000);
  130. }
  131. if (endParam) {
  132. data[endParam] = Math.round(+rangeEnd / 1000);
  133. }
  134. pushLoading();
  135. $.ajax($.extend({}, ajaxDefaults, source, {
  136. data: data,
  137. success: function(events) {
  138. events = events || [];
  139. var res = applyAll(success, this, arguments);
  140. if ($.isArray(res)) {
  141. events = res;
  142. }
  143. callback(events);
  144. },
  145. error: function() {
  146. applyAll(error, this, arguments);
  147. callback();
  148. },
  149. complete: function() {
  150. applyAll(complete, this, arguments);
  151. popLoading();
  152. }
  153. }));
  154. }else{
  155. callback();
  156. }
  157. }
  158. }
  159. /* Sources
  160. -----------------------------------------------------------------------------*/
  161. function addEventSource(source) {
  162. source = _addEventSource(source);
  163. if (source) {
  164. pendingSourceCnt++;
  165. fetchEventSource(source, currentFetchID); // will eventually call reportEvents
  166. }
  167. }
  168. function _addEventSource(source) {
  169. if ($.isFunction(source) || $.isArray(source)) {
  170. source = { events: source };
  171. }
  172. else if (typeof source == 'string') {
  173. source = { url: source };
  174. }
  175. if (typeof source == 'object') {
  176. normalizeSource(source);
  177. sources.push(source);
  178. return source;
  179. }
  180. }
  181. function removeEventSource(source) {
  182. sources = $.grep(sources, function(src) {
  183. return !isSourcesEqual(src, source);
  184. });
  185. // remove all client events from that source
  186. cache = $.grep(cache, function(e) {
  187. return !isSourcesEqual(e.source, source);
  188. });
  189. reportEvents(cache);
  190. }
  191. /* Manipulation
  192. -----------------------------------------------------------------------------*/
  193. function updateEvent(event) { // update an existing event
  194. var i, len = cache.length, e,
  195. defaultEventEnd = getView().defaultEventEnd, // getView???
  196. startDelta = event.start - event._start,
  197. endDelta = event.end ?
  198. (event.end - (event._end || defaultEventEnd(event))) // event._end would be null if event.end
  199. : 0; // was null and event was just resized
  200. for (i=0; i<len; i++) {
  201. e = cache[i];
  202. if (e._id == event._id && e != event) {
  203. e.start = new Date(+e.start + startDelta);
  204. if (event.end) {
  205. if (e.end) {
  206. e.end = new Date(+e.end + endDelta);
  207. }else{
  208. e.end = new Date(+defaultEventEnd(e) + endDelta);
  209. }
  210. }else{
  211. e.end = null;
  212. }
  213. e.title = event.title;
  214. e.url = event.url;
  215. e.allDay = event.allDay;
  216. e.className = event.className;
  217. e.editable = event.editable;
  218. e.color = event.color;
  219. e.backgroundColor = event.backgroundColor;
  220. e.borderColor = event.borderColor;
  221. e.textColor = event.textColor;
  222. normalizeEvent(e);
  223. }
  224. }
  225. normalizeEvent(event);
  226. reportEvents(cache);
  227. }
  228. function renderEvent(event, stick) {
  229. normalizeEvent(event);
  230. if (!event.source) {
  231. if (stick) {
  232. stickySource.events.push(event);
  233. event.source = stickySource;
  234. }
  235. cache.push(event);
  236. }
  237. reportEvents(cache);
  238. }
  239. function removeEvents(filter) {
  240. if (!filter) { // remove all
  241. cache = [];
  242. // clear all array sources
  243. for (var i=0; i<sources.length; i++) {
  244. if ($.isArray(sources[i].events)) {
  245. sources[i].events = [];
  246. }
  247. }
  248. }else{
  249. if (!$.isFunction(filter)) { // an event ID
  250. var id = filter + '';
  251. filter = function(e) {
  252. return e._id == id;
  253. };
  254. }
  255. cache = $.grep(cache, filter, true);
  256. // remove events from array sources
  257. for (var i=0; i<sources.length; i++) {
  258. if ($.isArray(sources[i].events)) {
  259. sources[i].events = $.grep(sources[i].events, filter, true);
  260. }
  261. }
  262. }
  263. reportEvents(cache);
  264. }
  265. function clientEvents(filter) {
  266. if ($.isFunction(filter)) {
  267. return $.grep(cache, filter);
  268. }
  269. else if (filter) { // an event ID
  270. filter += '';
  271. return $.grep(cache, function(e) {
  272. return e._id == filter;
  273. });
  274. }
  275. return cache; // else, return all
  276. }
  277. /* Loading State
  278. -----------------------------------------------------------------------------*/
  279. function pushLoading() {
  280. if (!loadingLevel++) {
  281. trigger('loading', null, true);
  282. }
  283. }
  284. function popLoading() {
  285. if (!--loadingLevel) {
  286. trigger('loading', null, false);
  287. }
  288. }
  289. /* Event Normalization
  290. -----------------------------------------------------------------------------*/
  291. function normalizeEvent(event) {
  292. var source = event.source || {};
  293. var ignoreTimezone = firstDefined(source.ignoreTimezone, options.ignoreTimezone);
  294. event._id = event._id || (event.id === undefined ? '_fc' + eventGUID++ : event.id + '');
  295. if (event.date) {
  296. if (!event.start) {
  297. event.start = event.date;
  298. }
  299. delete event.date;
  300. }
  301. event._start = cloneDate(event.start = parseDate(event.start, ignoreTimezone));
  302. event.end = parseDate(event.end, ignoreTimezone);
  303. if (event.end && event.end <= event.start) {
  304. event.end = null;
  305. }
  306. event._end = event.end ? cloneDate(event.end) : null;
  307. if (event.allDay === undefined) {
  308. event.allDay = firstDefined(source.allDayDefault, options.allDayDefault);
  309. }
  310. if (event.className) {
  311. if (typeof event.className == 'string') {
  312. event.className = event.className.split(/\s+/);
  313. }
  314. }else{
  315. event.className = [];
  316. }
  317. // TODO: if there is no start date, return false to indicate an invalid event
  318. }
  319. /* Utils
  320. ------------------------------------------------------------------------------*/
  321. function normalizeSource(source) {
  322. if (source.className) {
  323. // TODO: repeat code, same code for event classNames
  324. if (typeof source.className == 'string') {
  325. source.className = source.className.split(/\s+/);
  326. }
  327. }else{
  328. source.className = [];
  329. }
  330. var normalizers = fc.sourceNormalizers;
  331. for (var i=0; i<normalizers.length; i++) {
  332. normalizers[i](source);
  333. }
  334. }
  335. function isSourcesEqual(source1, source2) {
  336. return source1 && source2 && getSourcePrimitive(source1) == getSourcePrimitive(source2);
  337. }
  338. function getSourcePrimitive(source) {
  339. return ((typeof source == 'object') ? (source.events || source.url) : '') || source;
  340. }
  341. }