Our powerful JS Calendar component


Post by tokytok »

Hi,
I would like to customize the agenda view.
I need to add an image and a description on the event render cell.
I try this :

eventRenderer : ({ eventRecord, renderData }) => {
    ...
    return `<div class="agenda-event-container">
        <img src="${img}"/>
        <h6>${discipline}</h6>
        <p>${description}</p>
    </div>`;
}

The problem is the fix height of the cell (25px on .b-cal-event).
Is it possible to override it ?


Post by Animal »

The Calendar needs to reconfigure the agenda view slightly:

modes : {
    agenda : {
        eventHeight : 'auto'
    }
}

Post by tokytok »

Thank you. I try your solution but now, all event are superposed :
Image

I try to change the height of .b-cal-agenda-grid-row from 45px to 100% but the result is the same.
Do you have a solution ?


Post by Animal »

OK, we seem to be lacking some capability.

We have a new ticket for this: https://github.com/bryntum/support/issues/1898

The following overrides should help.

For AgendaColumn:

    defaultRenderer({ cellElement : targetElement, record, grid, size, rowElement }) {
        const
            {
                events,
                date
            }        = record,
            children = [];

    rowElement.classList.add(grid.agendaRowCls);
    targetElement.classList[record.isNonWorking ? 'add' : 'remove']('b-nonworking-day');
    targetElement.classList[grid.enableSticky ? 'add' : 'remove']('b-sticky-cell');

    for (let i = 0; i < events.length; i++) {
        const
            eventRecord    = events[i],
            eventEndDate   = eventRecord.endDate || DH.add(eventRecord.startDate, eventRecord.duration, eventRecord.durationUnit),
            isOverflow     = eventRecord.startDate < date,
            overflows      = eventEndDate > record.tomorrow,
            eventDomConfig = grid.internalEventRenderer({
                date     : record.date,
                isAllDay : eventRecord.allDay || overflows || isOverflow,
                eventRecord
            });

        // Arrow shows that it continues
        eventDomConfig.className['b-continues-right'] = overflows;
        eventDomConfig.className['b-continues-left'] = isOverflow;

        children.push({
            style : {
                marginBottom : `${grid.eventRowSpacing}px`
            },
            className : {
                'b-cal-agenda-event-row' : 1
            },
            dataset : {
                rowId : eventRecord.id
            },
            children : [
                grid.internalEventTimeRenderer(eventRecord),
                eventDomConfig
            ],

            // Match existing data-rowId elements first and ensure DOM order matches
            // children order.
            syncOptions : {
                syncIdField      : 'rowId',
                releaseThreshold : 0,
                strict           : true
            }
        });
    }

    if (grid._cellRenderer) {
        grid._cellRenderer(...arguments);
    }

    DomSync.sync({
        domConfig : {
            dataset : {
                date : DH.format(date, 'YYYY-MM-DD')
            },
            children : [
                grid.internalAgendaDateRenderer(date),
                {
                    className : {
                        'b-cal-event-bar-container' : 1
                    },
                    children
                }
            ],

            // Match existing data-row-id elements first and ensure DOM order matches
            // children order.
            syncOptions : {
                syncIdField      : 'rowId',
                releaseThreshold : 0,
                strict           : true
            }
        },
        targetElement
    });

    // THIS IS THE FIX. Code to record the row's height has moved to here.
    // NOTE that any images in the rendered data MUST have CSS-defined
    // heights so that the scrollHeight can be measured.
    size.height = grid.eventRenderer ? targetElement.scrollHeight : Math.max(record.events.length * (grid.eventHeight + grid.eventRowSpacing) - grid.eventRowSpacing + 40, 86);
}

For AgendaView :

    internalEventRenderer(renderData) {
        const
            me = this,
            {
                eventRenderer,
                eventHeight,
                eventArrows,
                intradayCls,
                alldayCls,
                pastEventCls,
                showTime,
                timeFormat
            }               = me,
            calendar        = me.up('calendar'),
            {
                eventRecord,
                date
            }               = renderData,
            eventEndDate    = eventRecord.endDate || DH.add(eventRecord.startDate, eventRecord.duration, eventRecord.durationUnit),
            resourceRecord  = eventRecord.isOccurrence ? eventRecord.recurringEvent.resource : eventRecord.resource,
            isRecurring     = eventRecord.isRecurring || eventRecord.isOccurrence,
            isAllDay        = ('isAllDay' in renderData) ? renderData.isAllDay : (eventRecord.allDay || eventRecord.isInterDay),
            arrowWidth      = eventHeight / 3,
            arrowHeight     = eventHeight / 2,
            startArrowStyle = {
                'border-width' : `${arrowHeight}px ${arrowWidth}px ${arrowHeight}px 0`
            },
            eventInnerStyle = {
                height : DomHelper.setLength(eventHeight)
            },
            endArrowStyle   = {
                'border-width' : `${arrowHeight}px 0 ${arrowHeight}px ${arrowWidth}px`
            };

    // Allow subclasses to create body content differently.
    // DayView will create different content layout.
    let bodyContent = me.internalBodyContentRenderer(eventRecord);

    // Make DomClassList copies for renderers to mutate.
    // We add our essential classes in after the renderer has run
    // then use these in the DomConfig object
    renderData.cls = eventRecord.cls.clone();
    renderData.iconCls = new DomClassList(eventRecord.iconCls); // Not a DomClassList, so not cloneable
    renderData.style = Object.assign(DomHelper.parseStyle(resourceRecord?.eventStyle), DomHelper.parseStyle(eventRecord.style));
    renderData.eventColor = eventRecord.color || eventRecord.eventColor || resourceRecord?.eventColor || emptyString;
    renderData.eventHeight = eventHeight;

    if (resourceRecord?.cls) {
        renderData.cls.add(resourceRecord.cls);
    }

    if (eventRenderer) {
        // Renderer may set renderedEvent style and cls
        const rendererValue = me.callback(eventRenderer, me, [{
            eventRecord,
            resourceRecord,
            renderData
        }]);

        // Allow renderer to change the event height <===== THIS BLOCK IS THE FIX ========<<<<<<
        if (renderData.eventHeight !== eventHeight) {
            eventInnerStyle.height = DomHelper.setLength(renderData.eventHeight);
        }

        if (rendererValue != null) {
            bodyContent = rendererValue;
        }

        // If the renderer has replaced the DomClassList with a string, promote back to a DomClassList
        if (typeof renderData.cls === 'string') {
            renderData.cls = new DomClassList(renderData.cls);
        }

        // Same goes for iconCls
        if (typeof renderData.iconCls === 'string') {
            renderData.iconCls = new DomClassList(renderData.iconCls);
        }

        // If the renderer set it to be a string, reinstate it as an object so we can add our essential styles
        if (typeof renderData.style === 'string') {
            renderData.style = DomHelper.parseStyle(renderData.style);
        }
    }
    bodyContent = [{
        className : 'b-cal-event-desc',
        html      : bodyContent
    }];

    // Some views mix intra and multi day events, but want the icons
    // to line up. AgendaView defaults this to true.
    if (me.alignIconOfStartsBeforeEvents && date && eventRecord.startDate < date) {
        renderData.style.marginLeft = `-${arrowWidth}px`;
    }

    // Set this after renderer has seen the data
    renderData.style.marginBottom = `${me.eventSpacing}px`;

    // Add essential classes for eventWrap *after* the renderer has run
    Object.assign(renderData.cls, {
        'b-cal-event-wrap' : 1,
        [alldayCls]        : alldayCls && isAllDay,
        [intradayCls]      : intradayCls && !isAllDay,
        [pastEventCls]     : eventEndDate < new Date(),
        'b-selected'       : calendar?.isEventSelected(eventRecord)
    });

    const color = renderData.eventColor;

    if (color) {
        if (color.startsWith('#')) {
            // Always set the color styles in case a previously set colour has been unset. In this case, we
            // set the value to an empty String object in order to avoid any falsy traps in called code.
            // The style value will always be set to ''
            if (isAllDay) {
                // The border color is inherited by the start and end arrows which signify
                // pointers to previous and next week. The bar element does not have a border.
                renderData.style['border-color'] = eventInnerStyle['background-color'] = color;
            }
            else {
                eventInnerStyle[me.eventColourStyleProperty] = color;
            }
        }
        // None-hex colors are take to be one of the predefined colors
        else if (color !== emptyString) {
            renderData.cls[`b-cal-color-${color}`] = 1;
        }
    }

    if (showTime && !isAllDay) {
        bodyContent.unshift({
            className : 'b-event-header',
            children  : [
                {
                    className : 'b-event-time',
                    html      : DH.format(eventRecord.startDate, timeFormat)
                }
            ]
        });
    }

    const
        hasIcon             = Boolean(renderData.iconCls.length), // Pull iconCls out of the renderData
        iconStyle           = me.iconTarget === 'header' ? { color } : emptyObject,
        useIconAsRecurrIcon = !hasIcon && isRecurring,
        showCircle          = me.showCircle && !hasIcon && !isRecurring,
        iconElement         = {
            tag       : 'i',
            className : Object.assign({
                'b-cal-event-icon'      : !useIconAsRecurrIcon,
                'b-cal-recurrence-icon' : useIconAsRecurrIcon,
                'b-icon'                : 1,
                'b-fw-icon'             : 1,
                'b-icon-circle'         : showCircle,
                'b-icon-recurring'      : useIconAsRecurrIcon
            }, renderData.iconCls),
            style : iconStyle
        },
        eventInnerContent   = [{
            className : 'b-cal-event-body',
            children  : bodyContent
        }],
        iconParent          = me.iconTarget === 'header' ? (bodyContent[0].children.length > 0 ? bodyContent[0].children : bodyContent) : eventInnerContent;

    if (me.iconTarget === 'header') {
        iconParent.push(iconElement);
    }
    else {
        iconParent.unshift(iconElement);
    }

    // If the event had its own icon and is recurring, the recurrence icon is extra
    if (hasIcon && isRecurring) {
        iconParent.push({
            tag       : 'i',
            className : {
                'b-cal-recurrence-icon' : 1,
                'b-icon'                : 1,
                'b-fw-icon'             : 1,
                'b-icon-recurring'      : 1
            },
            style : iconStyle
        });
    }

    return {
        // Events are tabbable
        tabIndex : 0,

        dataset : {
            eventId : eventRecord.id
        },
        className : renderData.cls,
        style     : renderData.style,
        children  : [eventArrows ? {
            className : 'b-start-arrow',
            style     : startArrowStyle
        } : null, {
            className : 'b-cal-event',
            style     : eventInnerStyle,
            children  : eventInnerContent
        }, eventArrows ? {
            className : 'b-end-arrow',
            style     : endArrowStyle
        } : null]
    };
}

And in your app's SCSS file:

.b-cal-agenda-event-row {
    // Images must have  defined height so that the rendered cell can be measured
    // By AgendaColumn's defaultRenderer
    .b-event-image {
        height : 100px; 
    }
}

And in your custom eventRenderer:

eventRenderer : ({ eventRecord, renderData }) => {
    ...
    // Informs AgendaColumn's rendering that it must *measure* the content height 
    // instead of multiplying its configured eventHeight by the number of events in that day.
    renderData.eventHeight = 'auto'; 

return `<div class="agenda-event-container">
    <img class="b-event-image" src="${img}"/>
    <h6>${discipline}</h6>
    <p>${description}</p>
</div>`;
}

Post by Animal »

Is there some CSS from your page affecting the button layout in the Calendar's toolbar?

That text should be vertically centred. See our online examples.

Image

Attachments
Screenshot 2020-11-18 at 11.23.44.png
Screenshot 2020-11-18 at 11.23.44.png (10.52 KiB) Viewed 1246 times

Post by tokytok »

Or just with css ... it work :)

#b-agendaview-1  {
    .b-cal-agenda-grid-row {
        height: auto;
    }

.b-grid-cell.b-calendar-cell {
    display: contents;
    height: auto;
}
.b-grid-row.b-cal-agenda-grid-row {
    position: relative;
    padding-top: 15px;
    padding-bottom: 7px;
}

.b-grid-subgrid.b-grid-horizontal-scroller {
    display: block!important;
    width: 100%!important;
}

.b-cal-event-bar-container {
    height: auto!important;
}

   .b-cal-event-wrap, .b-cal-event-body, .b-cal-event-desc {
        width: 100%;
    }

.b-cal-agenda-date {
    padding-top: 30px;
}
}

Post Reply