summaryrefslogtreecommitdiff
path: root/addons/web/static/src/js/libs/fullcalendar.js
diff options
context:
space:
mode:
authorstephanchrst <stephanchrst@gmail.com>2022-05-10 21:51:50 +0700
committerstephanchrst <stephanchrst@gmail.com>2022-05-10 21:51:50 +0700
commit3751379f1e9a4c215fb6eb898b4ccc67659b9ace (patch)
treea44932296ef4a9b71d5f010906253d8c53727726 /addons/web/static/src/js/libs/fullcalendar.js
parent0a15094050bfde69a06d6eff798e9a8ddf2b8c21 (diff)
initial commit 2
Diffstat (limited to 'addons/web/static/src/js/libs/fullcalendar.js')
-rw-r--r--addons/web/static/src/js/libs/fullcalendar.js252
1 files changed, 252 insertions, 0 deletions
diff --git a/addons/web/static/src/js/libs/fullcalendar.js b/addons/web/static/src/js/libs/fullcalendar.js
new file mode 100644
index 00000000..7f9714e8
--- /dev/null
+++ b/addons/web/static/src/js/libs/fullcalendar.js
@@ -0,0 +1,252 @@
+odoo.define('/web/static/src/js/libs/fullcalendar.js', function () {
+ "use strict";
+
+ function createYearCalendarView(FullCalendar) {
+ const {
+ Calendar,
+ createElement,
+ EventApi,
+ memoizeRendering,
+ View,
+ } = FullCalendar;
+
+ class YearView extends View {
+ constructor() {
+ super(...arguments);
+ this.months = null;
+ this.renderSubCalendarsMem = memoizeRendering(
+ this.renderSubCalendars, this.unrenderSubCalendars);
+ this.events = [];
+ }
+
+ //----------------------------------------------------------------------
+ // Getters
+ //----------------------------------------------------------------------
+
+ get currentDate() {
+ return this.context.calendar.state.currentDate;
+ }
+
+ //----------------------------------------------------------------------
+ // Public
+ //----------------------------------------------------------------------
+
+ /**
+ * @override
+ */
+ destroy() {
+ this.renderSubCalendarsMem.unrender();
+ super.destroy();
+ }
+ /**
+ * Removes the selection on sub calendar.
+ * Selections on sub calendars are not propagated to this view so
+ * this view cannot manage them.
+ */
+ unselect() {
+ for (const { calendar } of this.months) {
+ calendar.unselect();
+ }
+ }
+ /**
+ * @override
+ */
+ render() {
+ this.renderSubCalendarsMem(this.context);
+ super.render(...arguments);
+ }
+ /**
+ * Renders the main layout (the 4x3 month grid)
+ */
+ renderSubCalendars() {
+ this.el.classList.add('fc-scroller');
+ if (!this.context.options.selectable) {
+ this.el.classList.add('fc-readonly-year-view');
+ }
+ this.months = [];
+ for (let monthNumber = 0; monthNumber < 12; monthNumber++) {
+ const monthDate = new Date(this.currentDate.getFullYear(), monthNumber);
+ const monthShortName = moment(monthDate).format('MMM').toLowerCase();
+ const container = createElement('div', { class: 'fc-month-container' });
+ this.el.appendChild(container);
+ const el = createElement('div', {
+ class: `fc-month fc-month-${monthShortName}`,
+ });
+ container.appendChild(el);
+ const calendar = this._createMonthCalendar(el, monthDate);
+ this.months.push({ el, calendar });
+ calendar.render();
+ }
+ }
+ /**
+ * Removes the main layout (the 4x3 month grid).
+ * Called when view is switched/destroyed.
+ */
+ unrenderSubCalendars() {
+ for (const { el, calendar } of this.months) {
+ calendar.destroy();
+ el.remove();
+ }
+ }
+ /**
+ * Renders events in sub calendars.
+ * Called every time event source changed (when changing the date,
+ * when changing filters, adding/removing filters).
+ */
+ renderEvents() {
+ // `renderDates` also renders events so if it's called just before
+ // then do not execute this as it will do a re-render.
+ if (this.datesRendered) {
+ this.datesRendered = false;
+ return;
+ }
+ this.events = this._computeEvents();
+ for (const { calendar } of this.months) {
+ calendar.refetchEvents();
+ }
+ this._setCursorOnEventDates();
+ }
+ /**
+ * Renders dates and events in sub calendars.
+ * Called when the year of the date changed to render a new
+ * 4*3 grid of month calendar based on the new year.
+ */
+ renderDates() {
+ this.events = this._computeEvents();
+ for (const [monthNumber, { calendar }] of Object.entries(this.months)) {
+ const monthDate = new Date(this.currentDate.getFullYear(), monthNumber);
+ calendar.gotoDate(monthDate);
+ }
+ this._setCursorOnEventDates();
+ this.datesRendered = true;
+ }
+
+ //----------------------------------------------------------------------
+ // Private
+ //----------------------------------------------------------------------
+
+ /**
+ * @private
+ */
+ _computeEvents() {
+ const calendar = this.context.calendar;
+ return calendar.getEvents().map(event => {
+ const endUTC = calendar.dateEnv.toDate(event._instance.range.end);
+ const end = new Date(event._instance.range.end);
+ if (endUTC.getHours() > 0 || endUTC.getMinutes() > 0 ||
+ endUTC.getSeconds() > 0 || endUTC.getMilliseconds() > 0) {
+ end.setDate(end.getDate() + 1);
+ }
+ // clone event data to not trigger rerendering and issues
+ const instance = Object.assign({}, event._instance, {
+ range: { start: new Date(event._instance.range.start), end },
+ });
+ const def = Object.assign({}, event._def, {
+ rendering: 'background',
+ allDay: true,
+ });
+ return new EventApi(this.context.calendar, def, instance);
+ });
+ }
+ /**
+ * Create a month calendar for the date `monthDate` and mount it on container.
+ *
+ * @private
+ * @param {HTMLElement} container
+ * @param {Date} monthDate
+ */
+ _createMonthCalendar(container, monthDate) {
+ return new Calendar(container, Object.assign({}, this.context.options, {
+ defaultDate: monthDate,
+ defaultView: 'dayGridMonth',
+ header: { left: false, center: 'title', right: false },
+ titleFormat: { month: 'short', year: 'numeric' },
+ height: 0,
+ contentHeight: 0,
+ weekNumbers: false,
+ showNonCurrentDates: false,
+ views: {
+ dayGridMonth: {
+ columnHeaderText: (date) => moment(date).format("ddd")[0],
+ },
+ },
+ selectMinDistance: 5, // needed to not trigger select when click
+ dateClick: this._onYearDateClick.bind(this),
+ datesRender: undefined,
+ events: (info, successCB) => {
+ successCB(this.events);
+ },
+ windowResize: undefined,
+ }));
+ }
+ /**
+ * Sets fc-has-event class on every dates that have at least one event.
+ *
+ * @private
+ */
+ _setCursorOnEventDates() {
+ for (const el of this.el.querySelectorAll('.fc-has-event')) {
+ el.classList.remove('fc-has-event');
+ }
+ for (const event of Object.values(this.events)) {
+ let currentDate = moment(event._instance.range.start);
+ while (currentDate.isBefore(event._instance.range.end, 'day')) {
+ const formattedDate = currentDate.format('YYYY-MM-DD');
+ const el = this.el.querySelector(`.fc-day-top[data-date="${formattedDate}"]`);
+ if (el) {
+ el.classList.add('fc-has-event');
+ }
+ currentDate.add(1, 'days');
+ }
+ }
+ }
+
+ //----------------------------------------------------------------------
+ // Handlers
+ //----------------------------------------------------------------------
+
+ /**
+ * @private
+ * @param {*} info
+ */
+ _onYearDateClick(info) {
+ const calendar = this.context.calendar;
+ const events = Object.values(this.events)
+ .filter(event => {
+ const startUTC = calendar.dateEnv.toDate(event._instance.range.start);
+ const endUTC = calendar.dateEnv.toDate(event._instance.range.end);
+ const start = moment(startUTC);
+ const end = moment(endUTC);
+ const inclusivity = start.isSame(end, 'day') ? '[]' : '[)';
+ return moment(info.date).isBetween(start, end, 'day', inclusivity);
+ })
+ .map(event => {
+ return Object.assign({}, event._def, event._instance.range);
+ });
+ const yearDateInfo = Object.assign({}, info, {
+ view: this,
+ monthView: info.view,
+ events,
+ selectable: this.context.options.selectable,
+ });
+ calendar.publiclyTrigger('yearDateClick', [yearDateInfo]);
+ }
+ }
+
+ return FullCalendar.createPlugin({
+ views: {
+ dayGridYear: {
+ class: YearView,
+ duration: { years: 1 },
+ defaults: {
+ fixedWeekCount: true,
+ },
+ },
+ },
+ });
+ }
+
+ return {
+ createYearCalendarView,
+ };
+});