main.esm.js 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348
  1. /*!
  2. FullCalendar List View Plugin v4.4.2
  3. Docs & License: https://fullcalendar.io/
  4. (c) 2019 Adam Shaw
  5. */
  6. import { getAllDayHtml, isMultiDayRange, htmlEscape, FgEventRenderer, memoize, memoizeRendering, ScrollComponent, subtractInnerElHeight, sliceEventStore, intersectRanges, htmlToElement, createFormatter, createElement, buildGotoAnchorHtml, View, startOfDay, addDays, createPlugin } from '@fullcalendar/core';
  7. /*! *****************************************************************************
  8. Copyright (c) Microsoft Corporation.
  9. Permission to use, copy, modify, and/or distribute this software for any
  10. purpose with or without fee is hereby granted.
  11. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
  12. REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
  13. AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
  14. INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
  15. LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
  16. OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
  17. PERFORMANCE OF THIS SOFTWARE.
  18. ***************************************************************************** */
  19. /* global Reflect, Promise */
  20. var extendStatics = function(d, b) {
  21. extendStatics = Object.setPrototypeOf ||
  22. ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
  23. function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; };
  24. return extendStatics(d, b);
  25. };
  26. function __extends(d, b) {
  27. extendStatics(d, b);
  28. function __() { this.constructor = d; }
  29. d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
  30. }
  31. var ListEventRenderer = /** @class */ (function (_super) {
  32. __extends(ListEventRenderer, _super);
  33. function ListEventRenderer(listView) {
  34. var _this = _super.call(this) || this;
  35. _this.listView = listView;
  36. return _this;
  37. }
  38. ListEventRenderer.prototype.attachSegs = function (segs) {
  39. if (!segs.length) {
  40. this.listView.renderEmptyMessage();
  41. }
  42. else {
  43. this.listView.renderSegList(segs);
  44. }
  45. };
  46. ListEventRenderer.prototype.detachSegs = function () {
  47. };
  48. // generates the HTML for a single event row
  49. ListEventRenderer.prototype.renderSegHtml = function (seg) {
  50. var _a = this.context, theme = _a.theme, options = _a.options;
  51. var eventRange = seg.eventRange;
  52. var eventDef = eventRange.def;
  53. var eventInstance = eventRange.instance;
  54. var eventUi = eventRange.ui;
  55. var url = eventDef.url;
  56. var classes = ['fc-list-item'].concat(eventUi.classNames);
  57. var bgColor = eventUi.backgroundColor;
  58. var timeHtml;
  59. if (eventDef.allDay) {
  60. timeHtml = getAllDayHtml(options);
  61. }
  62. else if (isMultiDayRange(eventRange.range)) {
  63. if (seg.isStart) {
  64. timeHtml = htmlEscape(this._getTimeText(eventInstance.range.start, seg.end, false // allDay
  65. ));
  66. }
  67. else if (seg.isEnd) {
  68. timeHtml = htmlEscape(this._getTimeText(seg.start, eventInstance.range.end, false // allDay
  69. ));
  70. }
  71. else { // inner segment that lasts the whole day
  72. timeHtml = getAllDayHtml(options);
  73. }
  74. }
  75. else {
  76. // Display the normal time text for the *event's* times
  77. timeHtml = htmlEscape(this.getTimeText(eventRange));
  78. }
  79. if (url) {
  80. classes.push('fc-has-url');
  81. }
  82. return '<tr class="' + classes.join(' ') + '">' +
  83. (this.displayEventTime ?
  84. '<td class="fc-list-item-time ' + theme.getClass('widgetContent') + '">' +
  85. (timeHtml || '') +
  86. '</td>' :
  87. '') +
  88. '<td class="fc-list-item-marker ' + theme.getClass('widgetContent') + '">' +
  89. '<span class="fc-event-dot"' +
  90. (bgColor ?
  91. ' style="background-color:' + bgColor + '"' :
  92. '') +
  93. '></span>' +
  94. '</td>' +
  95. '<td class="fc-list-item-title ' + theme.getClass('widgetContent') + '">' +
  96. '<a' + (url ? ' href="' + htmlEscape(url) + '"' : '') + '>' +
  97. htmlEscape(eventDef.title || '') +
  98. '</a>' +
  99. '</td>' +
  100. '</tr>';
  101. };
  102. // like "4:00am"
  103. ListEventRenderer.prototype.computeEventTimeFormat = function () {
  104. return {
  105. hour: 'numeric',
  106. minute: '2-digit',
  107. meridiem: 'short'
  108. };
  109. };
  110. return ListEventRenderer;
  111. }(FgEventRenderer));
  112. /*
  113. Responsible for the scroller, and forwarding event-related actions into the "grid".
  114. */
  115. var ListView = /** @class */ (function (_super) {
  116. __extends(ListView, _super);
  117. function ListView(viewSpec, parentEl) {
  118. var _this = _super.call(this, viewSpec, parentEl) || this;
  119. _this.computeDateVars = memoize(computeDateVars);
  120. _this.eventStoreToSegs = memoize(_this._eventStoreToSegs);
  121. _this.renderSkeleton = memoizeRendering(_this._renderSkeleton, _this._unrenderSkeleton);
  122. var eventRenderer = _this.eventRenderer = new ListEventRenderer(_this);
  123. _this.renderContent = memoizeRendering(eventRenderer.renderSegs.bind(eventRenderer), eventRenderer.unrender.bind(eventRenderer), [_this.renderSkeleton]);
  124. return _this;
  125. }
  126. ListView.prototype.firstContext = function (context) {
  127. context.calendar.registerInteractiveComponent(this, {
  128. el: this.el
  129. // TODO: make aware that it doesn't do Hits
  130. });
  131. };
  132. ListView.prototype.render = function (props, context) {
  133. _super.prototype.render.call(this, props, context);
  134. var _a = this.computeDateVars(props.dateProfile), dayDates = _a.dayDates, dayRanges = _a.dayRanges;
  135. this.dayDates = dayDates;
  136. this.renderSkeleton(context);
  137. this.renderContent(context, this.eventStoreToSegs(props.eventStore, props.eventUiBases, dayRanges));
  138. };
  139. ListView.prototype.destroy = function () {
  140. _super.prototype.destroy.call(this);
  141. this.renderSkeleton.unrender();
  142. this.renderContent.unrender();
  143. this.context.calendar.unregisterInteractiveComponent(this);
  144. };
  145. ListView.prototype._renderSkeleton = function (context) {
  146. var theme = context.theme;
  147. this.el.classList.add('fc-list-view');
  148. var listViewClassNames = (theme.getClass('listView') || '').split(' '); // wish we didn't have to do this
  149. for (var _i = 0, listViewClassNames_1 = listViewClassNames; _i < listViewClassNames_1.length; _i++) {
  150. var listViewClassName = listViewClassNames_1[_i];
  151. if (listViewClassName) { // in case input was empty string
  152. this.el.classList.add(listViewClassName);
  153. }
  154. }
  155. this.scroller = new ScrollComponent('hidden', // overflow x
  156. 'auto' // overflow y
  157. );
  158. this.el.appendChild(this.scroller.el);
  159. this.contentEl = this.scroller.el; // shortcut
  160. };
  161. ListView.prototype._unrenderSkeleton = function () {
  162. // TODO: remove classNames
  163. this.scroller.destroy(); // will remove the Grid too
  164. };
  165. ListView.prototype.updateSize = function (isResize, viewHeight, isAuto) {
  166. _super.prototype.updateSize.call(this, isResize, viewHeight, isAuto);
  167. this.eventRenderer.computeSizes(isResize);
  168. this.eventRenderer.assignSizes(isResize);
  169. this.scroller.clear(); // sets height to 'auto' and clears overflow
  170. if (!isAuto) {
  171. this.scroller.setHeight(this.computeScrollerHeight(viewHeight));
  172. }
  173. };
  174. ListView.prototype.computeScrollerHeight = function (viewHeight) {
  175. return viewHeight -
  176. subtractInnerElHeight(this.el, this.scroller.el); // everything that's NOT the scroller
  177. };
  178. ListView.prototype._eventStoreToSegs = function (eventStore, eventUiBases, dayRanges) {
  179. return this.eventRangesToSegs(sliceEventStore(eventStore, eventUiBases, this.props.dateProfile.activeRange, this.context.nextDayThreshold).fg, dayRanges);
  180. };
  181. ListView.prototype.eventRangesToSegs = function (eventRanges, dayRanges) {
  182. var segs = [];
  183. for (var _i = 0, eventRanges_1 = eventRanges; _i < eventRanges_1.length; _i++) {
  184. var eventRange = eventRanges_1[_i];
  185. segs.push.apply(segs, this.eventRangeToSegs(eventRange, dayRanges));
  186. }
  187. return segs;
  188. };
  189. ListView.prototype.eventRangeToSegs = function (eventRange, dayRanges) {
  190. var _a = this.context, dateEnv = _a.dateEnv, nextDayThreshold = _a.nextDayThreshold;
  191. var range = eventRange.range;
  192. var allDay = eventRange.def.allDay;
  193. var dayIndex;
  194. var segRange;
  195. var seg;
  196. var segs = [];
  197. for (dayIndex = 0; dayIndex < dayRanges.length; dayIndex++) {
  198. segRange = intersectRanges(range, dayRanges[dayIndex]);
  199. if (segRange) {
  200. seg = {
  201. component: this,
  202. eventRange: eventRange,
  203. start: segRange.start,
  204. end: segRange.end,
  205. isStart: eventRange.isStart && segRange.start.valueOf() === range.start.valueOf(),
  206. isEnd: eventRange.isEnd && segRange.end.valueOf() === range.end.valueOf(),
  207. dayIndex: dayIndex
  208. };
  209. segs.push(seg);
  210. // detect when range won't go fully into the next day,
  211. // and mutate the latest seg to the be the end.
  212. if (!seg.isEnd && !allDay &&
  213. dayIndex + 1 < dayRanges.length &&
  214. range.end <
  215. dateEnv.add(dayRanges[dayIndex + 1].start, nextDayThreshold)) {
  216. seg.end = range.end;
  217. seg.isEnd = true;
  218. break;
  219. }
  220. }
  221. }
  222. return segs;
  223. };
  224. ListView.prototype.renderEmptyMessage = function () {
  225. this.contentEl.innerHTML =
  226. '<div class="fc-list-empty-wrap2">' + // TODO: try less wraps
  227. '<div class="fc-list-empty-wrap1">' +
  228. '<div class="fc-list-empty">' +
  229. htmlEscape(this.context.options.noEventsMessage) +
  230. '</div>' +
  231. '</div>' +
  232. '</div>';
  233. };
  234. // called by ListEventRenderer
  235. ListView.prototype.renderSegList = function (allSegs) {
  236. var theme = this.context.theme;
  237. var segsByDay = this.groupSegsByDay(allSegs); // sparse array
  238. var dayIndex;
  239. var daySegs;
  240. var i;
  241. var tableEl = htmlToElement('<table class="fc-list-table ' + theme.getClass('tableList') + '"><tbody></tbody></table>');
  242. var tbodyEl = tableEl.querySelector('tbody');
  243. for (dayIndex = 0; dayIndex < segsByDay.length; dayIndex++) {
  244. daySegs = segsByDay[dayIndex];
  245. if (daySegs) { // sparse array, so might be undefined
  246. // append a day header
  247. tbodyEl.appendChild(this.buildDayHeaderRow(this.dayDates[dayIndex]));
  248. daySegs = this.eventRenderer.sortEventSegs(daySegs);
  249. for (i = 0; i < daySegs.length; i++) {
  250. tbodyEl.appendChild(daySegs[i].el); // append event row
  251. }
  252. }
  253. }
  254. this.contentEl.innerHTML = '';
  255. this.contentEl.appendChild(tableEl);
  256. };
  257. // Returns a sparse array of arrays, segs grouped by their dayIndex
  258. ListView.prototype.groupSegsByDay = function (segs) {
  259. var segsByDay = []; // sparse array
  260. var i;
  261. var seg;
  262. for (i = 0; i < segs.length; i++) {
  263. seg = segs[i];
  264. (segsByDay[seg.dayIndex] || (segsByDay[seg.dayIndex] = []))
  265. .push(seg);
  266. }
  267. return segsByDay;
  268. };
  269. // generates the HTML for the day headers that live amongst the event rows
  270. ListView.prototype.buildDayHeaderRow = function (dayDate) {
  271. var _a = this.context, theme = _a.theme, dateEnv = _a.dateEnv, options = _a.options;
  272. var mainFormat = createFormatter(options.listDayFormat); // TODO: cache
  273. var altFormat = createFormatter(options.listDayAltFormat); // TODO: cache
  274. return createElement('tr', {
  275. className: 'fc-list-heading',
  276. 'data-date': dateEnv.formatIso(dayDate, { omitTime: true })
  277. }, '<td class="' + (theme.getClass('tableListHeading') ||
  278. theme.getClass('widgetHeader')) + '" colspan="3">' +
  279. (mainFormat ?
  280. buildGotoAnchorHtml(options, dateEnv, dayDate, { 'class': 'fc-list-heading-main' }, htmlEscape(dateEnv.format(dayDate, mainFormat)) // inner HTML
  281. ) :
  282. '') +
  283. (altFormat ?
  284. buildGotoAnchorHtml(options, dateEnv, dayDate, { 'class': 'fc-list-heading-alt' }, htmlEscape(dateEnv.format(dayDate, altFormat)) // inner HTML
  285. ) :
  286. '') +
  287. '</td>');
  288. };
  289. return ListView;
  290. }(View));
  291. ListView.prototype.fgSegSelector = '.fc-list-item'; // which elements accept event actions
  292. function computeDateVars(dateProfile) {
  293. var dayStart = startOfDay(dateProfile.renderRange.start);
  294. var viewEnd = dateProfile.renderRange.end;
  295. var dayDates = [];
  296. var dayRanges = [];
  297. while (dayStart < viewEnd) {
  298. dayDates.push(dayStart);
  299. dayRanges.push({
  300. start: dayStart,
  301. end: addDays(dayStart, 1)
  302. });
  303. dayStart = addDays(dayStart, 1);
  304. }
  305. return { dayDates: dayDates, dayRanges: dayRanges };
  306. }
  307. var main = createPlugin({
  308. views: {
  309. list: {
  310. class: ListView,
  311. buttonTextKey: 'list',
  312. listDayFormat: { month: 'long', day: 'numeric', year: 'numeric' } // like "January 1, 2016"
  313. },
  314. listDay: {
  315. type: 'list',
  316. duration: { days: 1 },
  317. listDayFormat: { weekday: 'long' } // day-of-week is all we need. full date is probably in header
  318. },
  319. listWeek: {
  320. type: 'list',
  321. duration: { weeks: 1 },
  322. listDayFormat: { weekday: 'long' },
  323. listDayAltFormat: { month: 'long', day: 'numeric', year: 'numeric' }
  324. },
  325. listMonth: {
  326. type: 'list',
  327. duration: { month: 1 },
  328. listDayAltFormat: { weekday: 'long' } // day-of-week is nice-to-have
  329. },
  330. listYear: {
  331. type: 'list',
  332. duration: { year: 1 },
  333. listDayAltFormat: { weekday: 'long' } // day-of-week is nice-to-have
  334. }
  335. }
  336. });
  337. export default main;
  338. export { ListView };