FullCalendar.js 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139
  1. import { defineComponent, h, Fragment, Teleport } from 'vue';
  2. import { Calendar } from '@fullcalendar/core';
  3. import { CustomRenderingStore } from '@fullcalendar/core/internal';
  4. import { OPTION_IS_COMPLEX } from './options.js';
  5. const FullCalendar = defineComponent({
  6. props: {
  7. options: Object
  8. },
  9. data() {
  10. return {
  11. renderId: 0,
  12. customRenderingMap: new Map()
  13. };
  14. },
  15. methods: {
  16. getApi() {
  17. return getSecret(this).calendar;
  18. },
  19. buildOptions(suppliedOptions) {
  20. return {
  21. ...suppliedOptions,
  22. customRenderingMetaMap: kebabToCamelKeys(this.$slots),
  23. handleCustomRendering: getSecret(this).handleCustomRendering,
  24. };
  25. },
  26. },
  27. render() {
  28. const customRenderingNodes = [];
  29. for (const customRendering of this.customRenderingMap.values()) {
  30. customRenderingNodes.push(h(CustomRenderingComponent, {
  31. key: customRendering.id,
  32. customRendering,
  33. }));
  34. }
  35. return h('div', {
  36. // when renderId is changed, Vue will trigger a real-DOM async rerender, calling beforeUpdate/updated
  37. attrs: { 'data-fc-render-id': this.renderId }
  38. }, h(Fragment, customRenderingNodes)); // for containing CustomRendering keys
  39. },
  40. mounted() {
  41. const customRenderingStore = new CustomRenderingStore();
  42. getSecret(this).handleCustomRendering = customRenderingStore.handle.bind(customRenderingStore);
  43. const calendarOptions = this.buildOptions(this.options);
  44. const calendar = new Calendar(this.$el, calendarOptions);
  45. getSecret(this).calendar = calendar;
  46. calendar.render();
  47. customRenderingStore.subscribe((customRenderingMap) => {
  48. this.customRenderingMap = customRenderingMap; // likely same reference, so won't rerender
  49. this.renderId++; // force rerender
  50. getSecret(this).needCustomRenderingResize = true;
  51. });
  52. },
  53. beforeUpdate() {
  54. this.getApi().resumeRendering(); // the watcher handlers paused it
  55. },
  56. updated() {
  57. if (getSecret(this).needCustomRenderingResize) {
  58. getSecret(this).needCustomRenderingResize = false;
  59. this.getApi().updateSize();
  60. }
  61. },
  62. beforeUnmount() {
  63. this.getApi().destroy();
  64. },
  65. watch: buildWatchers()
  66. });
  67. export default FullCalendar;
  68. // Custom Rendering
  69. // -------------------------------------------------------------------------------------------------
  70. const CustomRenderingComponent = defineComponent({
  71. props: {
  72. customRendering: Object
  73. },
  74. render() {
  75. const customRendering = this.customRendering;
  76. const innerContent = typeof customRendering.generatorMeta === 'function' ?
  77. customRendering.generatorMeta(customRendering.renderProps) : // vue-normalized slot function
  78. customRendering.generatorMeta; // probably a vue JSX node returned from content-inject func
  79. return h(Teleport, { to: customRendering.containerEl }, innerContent);
  80. }
  81. });
  82. // storing internal state:
  83. // https://github.com/vuejs/vue/issues/1988#issuecomment-163013818
  84. function getSecret(inst) {
  85. return inst;
  86. }
  87. function buildWatchers() {
  88. let watchers = {
  89. // watches changes of ALL options and their nested objects,
  90. // but this is only a means to be notified of top-level non-complex options changes.
  91. options: {
  92. deep: true,
  93. handler(options) {
  94. let calendar = this.getApi();
  95. calendar.pauseRendering();
  96. let calendarOptions = this.buildOptions(options);
  97. calendar.resetOptions(calendarOptions);
  98. this.renderId++; // will queue a rerender
  99. }
  100. }
  101. };
  102. for (let complexOptionName in OPTION_IS_COMPLEX) {
  103. // handlers called when nested objects change
  104. watchers[`options.${complexOptionName}`] = {
  105. deep: true,
  106. handler(val) {
  107. // unfortunately the handler is called with undefined if new props were set, but the complex one wasn't ever set
  108. if (val !== undefined) {
  109. let calendar = this.getApi();
  110. calendar.pauseRendering();
  111. calendar.resetOptions({
  112. [complexOptionName]: val
  113. }, [complexOptionName]);
  114. this.renderId++; // will queue a rerender
  115. }
  116. }
  117. };
  118. }
  119. return watchers;
  120. }
  121. // General Utils
  122. // -------------------------------------------------------------------------------------------------
  123. function kebabToCamelKeys(map) {
  124. const newMap = {};
  125. for (const key in map) {
  126. newMap[kebabToCamel(key)] = map[key];
  127. }
  128. return newMap;
  129. }
  130. function kebabToCamel(s) {
  131. return s
  132. .split('-')
  133. .map((word, index) => index ? capitalize(word) : word)
  134. .join('');
  135. }
  136. function capitalize(s) {
  137. return s.charAt(0).toUpperCase() + s.slice(1);
  138. }
  139. //# sourceMappingURL=FullCalendar.js.map