Our blazing fast Grid component built with pure JavaScript


Post by SemFA »

Hello,

I'm having an issue with the Grid react component. Whenever I right-click a column in the grid, the dropdown shows up. All options work except for the submenu's and I can't click anywhere else to make it disappear. I don't think I've done anything weird. I've included the code below that doesn't work for me.

// grid.js
import { TimeStringHelper, __ } from "global";
import { Component } from "react";

import { BryntumGrid } from "@bryntum/grid-react";

import { Task } from "../stores/task.store";
import * as React from "react";

export default class Grid extends Component {
    grid = React.createRef();

configuration = {
    id: "to-be-planned",
    store: {
        modelClass: Task,
        readUrl: "/api/v1/planning/events/unassigned",
        autoLoad: true,
        headers: {
            "X-CSRF-TOKEN": document.querySelector("meta[name='csrf-token']").getAttribute("content")
        }
    },
    tbar: [`<h4>${__("To be planned")}</h4>`],
    features: {
        stripe: true,
        sort: "name"
    },
    columns: [
        {
            text: __("Unassigned tasks"),
            field: "name",
            htmlEncode: false,
            renderer: ({ record }) => `<i class="${record.iconCls}"></i>${record.name}`
        },
        {
            text: __("Site"),
            field: "site.name",
            htmlEncode: false
        },
        {
            text: __("Start date"),
            type: "date",
            editor: false,
            field: "visualStartDate",
            format: "DD-MM-YYYY"
        },
        {
            text: __("End date"),
            type: "date",
            editor: false,
            field: "visualEndDate",
            format: "DD-MM-YYYY",
            hidden: true
        },
        {
            text: __("Duration"),
            type: "number",
            editor: false,
            field: "total_duration",
            renderer: ({ record }) => TimeStringHelper.parseSeconds(record.total_duration)
        }
    ]
};

render() {
    return <BryntumGrid {...this.configuration} ref={this.grid} />;
}
}
// calendar.js
// React
import SplitterLayout from "react-splitter-layout";
import React, { Component } from "react";

// Bryntum
import BryntumCalendar from "../components/bryntum-calendar";
import { Popup } from "@bryntum/calendar/calendar.umd";

// To be planned grid
import Grid from "../components/grid";

// Stores
import { Resource } from "../stores/resource.store";
import { Task } from "../stores/task.store";

export default class CalendarPlanning extends Component {
    calendar = React.createRef();
    grid = React.createRef();

state = {
    plannedTasks: "",
    dayStartTime: "6:00",
    dayEndTime: "18:00",
    usersColumn: true,
    positionColumn: true,
    cityColumn: true,
    finalize: false,
    showAdvancedFiltersClass: ""
};

resources = null;

crudManager = {
    eventStore: {
        modelClass: Task
    },
    resourceStore: {
        modelClass: Resource
    },
    transport: {
        load: {
            url: "/api/v1/planning/crud",
            headers: {
                "X-CSRF-TOKEN": document.querySelector("meta[name='csrf-token']").getAttribute("content")
            }
        },
        sync: {
            url: "/api/v1/planning/crud",
            method: "PATCH",
            headers: {
                "X-CSRF-TOKEN": document.querySelector("meta[name='csrf-token']").getAttribute("content")
            }
        }
    },
    autoLoad: true
};

editor = {
    items: {
        siteSelector: {
            name: "site",
            type: "siteSelector",
            label: "Site",
            weight: 110
        },
        total_duration: {
            name: "total_duration",
            type: "textfield",
            label: "Duration",
            weight: 610
        },
        resourceField: {
            label: "Employee",
            multiSelect: true
        }
    },
    editorConfig: {
        autoClose: false
    },
    showRecurringUI: allowed("Planning", "addRecurrence")
};

constructor(props) {
    super(props);

    const storage = window.localStorage;

    const dayStartTime = storage.getItem("calendar_day_start_time") || "6:00";
    const dayEndTime = storage.getItem("calendar_day_end_time") || "18:00";

    const state = {
        ...this.state,
        dayStartTime,
        dayEndTime
    };

    this.state = state;
}

getGrid() {
    return this.grid.current.grid.current.gridInstance;
}

getCalendar() {
    return this.calendar?.current?.calendarInstance;
}

settings(event) {
    const { dayStartTime, dayEndTime } = this.state;

    let startDate = new Date();
    let endDate = new Date();

    const startHour = +dayStartTime.split(":")[0];
    const startMinute = +dayStartTime.split(":")[1];

    const endHour = +dayEndTime.split(":")[0];
    const endMinute = +dayEndTime.split(":")[1];

    startDate.setHours(startHour, startMinute, 0, 0);
    endDate.setHours(endHour, endMinute, 0, 0);

    new Popup({
        forElement: event.source.currentElement,
        items: [
            {
                type: "container",
                itemCls: "b-popup-horizontal",
                items: [
                    {
                        type: "timefield",
                        label: "Start time",
                        value: startDate,
                        step: "1h",
                        format: "HH:mm",
                        onChange: this.setDayStartTime.bind(this)
                    },
                    {
                        type: "timefield",
                        label: "End time",
                        value: endDate,
                        step: "1h",
                        format: "HH:mm",
                        onChange: this.setDayEndTime.bind(this)
                    }
                ]
            }
        ]
    });
}

setDayStartTime(event) {
    const dayStartTime = event.value.getHours() + ":" + event.value.getMinutes();

    this.setState({ dayStartTime });

    const storage = window.localStorage;
    storage.setItem("calendar_day_start_time", dayStartTime);
}

setDayEndTime(event) {
    const dayEndTime = event.value.getHours() + ":" + event.value.getMinutes();

    this.setState({ dayEndTime });

    const storage = window.localStorage;
    storage.setItem("calendar_day_end_time", dayEndTime);
}

render() {
    const { dayStartTime, dayEndTime } = this.state;

    const calendarConfig = {
        style: "height: 100%",
        ref: this.calendar,
        crudManager: this.crudManager,
        weekStartDay: 1,
        modes: {
            week: {
                dayStartTime,
                dayEndTime,
            },
        },
        scheduleMenuFeature: true,
        externalEventSourceFeature: {
            grid: "to-be-planned"
        }
    };

    return (
        <SplitterLayout percentage={true} secondaryInitialSize={40}>
            <BryntumCalendar {...calendarConfig} />
            <Grid ref={this.grid} />
        </SplitterLayout>
    );
}
}
//bryntum-calendar.js
import { BryntumCalendar as Calendar } from "@bryntum/calendar-react";

export default class BryntumCalendar extends Calendar {
    featureNames = [
        "cellEditFeature",
        "cellMenuFeature",
        "cellTooltipFeature",
        "columnAutoWidthFeature",
        "columnDragToolbarFeature",
        "columnLinesFeature",
        "columnPickerFeature",
        "columnReorderFeature",
        "columnResizeFeature",
        "contextMenuFeature",
        "dependenciesFeature",
        "dependencyEditFeature",
        "externalEventSourceFeature",
        "eventContextMenuFeature",
        "eventDragCreateFeature",
        "eventDragFeature",
        "eventDragSelectFeature",
        "eventEditFeature",
        "eventFilterFeature",
        "eventMenuFeature",
        "eventResizeFeature",
        "eventTooltipFeature",
        "excelExporterFeature",
        "filterBarFeature",
        "filterFeature",
        "groupFeature",
        "groupSummaryFeature",
        "headerContextMenuFeature",
        "headerMenuFeature",
        "headerZoomFeature",
        "labelsFeature",
        "loadOnDemandFeature",
        "nonWorkingTimeFeature",
        "panFeature",
        "pdfExportFeature",
        "printFeature",
        "quickFindFeature",
        "regionResizeFeature",
        "resourceTimeRangesFeature",
        "rowReorderFeature",
        "scheduleContextMenuFeature",
        "scheduleMenuFeature",
        "scheduleTooltipFeature",
        "searchFeature",
        "simpleEventEditFeature",
        "sortFeature",
        "stickyCellsFeature",
        "stripeFeature",
        "summaryFeature",
        "timeAxisHeaderMenuFeature",
        "timeRangesFeature",
        "treeFeature",
        "weekExpanderFeature"
    ];
}

when I replace the BryntumCalendar with a simple <div></div> it starts working fine. So I'm not entirely sure what's going on.


Post by pmiklashevich »

Could you please submit a runnable testcase showing the issue? Please read how to ask for help here: viewtopic.php?f=35&t=772

Pavlo Miklashevych
Sr. Frontend Developer


Post by SemFA »

I've created a very simple npx create-react-app and added the packages @bryntum/calendar, @bryntum/calendar-react, @bryntum/grid and @bryntum/grid-react. I then edited the App.js file and included a very basic version of what we use. I've attached the example. It's a simple react app, so yarn install && yarn start should do fine. This example simply adds a react calendar and a react grid with some basic columns without data and it shows my issue.

I've used calendar v4.1.6 and grid v4.1.6. This also happened in v4.1.5
I'm using the latest version of Firefox (89.0.1 (64-bit))
Windows 10 version 21H1 19043.1052

If you remove the calendar, it will start working again. It only happens when I also add the calendar.

Attachments
bryntum-example.zip
(458.93 KiB) Downloaded 95 times

Post by pmiklashevich »

Thanks! Your testcase is good! I was able to reproduce the issue. The problem is that there is a global widget registry. And since you combine 2 products, there are 2 registries. And when you show the menu on the grid, and you focus the menu item (Grid/lib/Core/GlobalEvents.js - focusin function), the widget is not found because the global reference is wrong.

 // This is null
toWidget = GlobalEvents.Widget.fromElement(toElement)

Therefore the menu is broken. You can try to workaround the issue by creating the Grid first. But there is no guarantee it will work smoothly.

// React
import React, { Component } from "react";

// Bryntum
import { BryntumCalendar } from "@bryntum/calendar-react";

// To be planned grid
import { BryntumGrid } from '@bryntum/grid-react';

import '@bryntum/calendar/calendar.material.css';
import '@bryntum/grid/grid.material.css';


export default class CalendarPlanning extends Component {
    render() {
        const calendarConfig = {
            weekStartDay: 1,
            height : 500
        };

    const gridConfig = {
        height : 50,
        columns : [
            { text: "Name", field: "name" }
        ]
    };

    return (
        <div>
            <BryntumGrid {...gridConfig} />
            <BryntumCalendar {...calendarConfig} />
        </div>
    );
}
}

This issue is about similar thing. I'll update it with your testcase. https://github.com/bryntum/support/issues/1980
This one is more like a parent ticket. We're planning to create slim build for every product to make it easy to combine them in one app. https://github.com/bryntum/support/issues/2805
Please subscribe to the tickets to get notified when they are done.

Pavlo Miklashevych
Sr. Frontend Developer


Post by SemFA »

Hey pavel,

thanks for the info, I've followed the issues on github. Sadly I can't switch them around as they're in a splitter-layout and i need the grid on the right side. I will wait for this issue to be fixed.

Any ETA on when v5.0 will be ready for release?


Post by pmiklashevich »

You can try to create a fake grid in constructor or add a hidden grid at the top.

No ETA sorry. 4.1.6 has been released and we're working on 4.2.0

Pavlo Miklashevych
Sr. Frontend Developer


Post by SemFA »

Thanks I've added a temp fix by adding an empty grid just above the other components in a display none container. This fixes the issue so that we can wait for the real fix.


Post by SemFA »

Seems like the temp fix doesn't work. When I initialize the grid first, dropdowns in the calendar will stop working correctly. Found the issue when changing a resource in the event editor. See screenshot, the dropdown from the selector has the same issues as grid has with the dropdown on right-clicking columns.

Attachments
Screenshot 2021-06-24 102814.png
Screenshot 2021-06-24 102814.png (32.12 KiB) Viewed 1406 times

Post by pmiklashevich »

Well this is still the same issue. Need to get this fixed properly.

Pavlo Miklashevych
Sr. Frontend Developer


Post Reply