Our blazing fast Grid component built with pure JavaScript


Post by peachaws »

joakim.l wrote: Tue Feb 13, 2024 11:08 am

Hi,

As long as you use the same version on all Bryntum packages in your package.json it should be fine.

On your old branch, you can use 5.4.0 on all packages. In your new branch, you can use 5.6.6 on all packages.

You will most likely have to run npm install after switching branches to have the correct packages installed.

Hi Joakim,

We tried the example you gave and it worked for us. The issue we are facing is described below.
We have added the listener to the row expander and did the hide/show of the grids/container based on the button clicks. We need to have the row expander event triggered on each button click but it does so for only one (currently it works for the button that is clicked first). You can refer to the code snippet provided below.

import { DataGenerator, RandomGenerator, DateHelper, Grid, GridRowModel, Store, StringHelper } from '../../build/grid.module.js?474079';
import shared from '../_shared/shared.module.js?474079';

const randomGenerator = new RandomGenerator();

export const getEmployees = () => {
    const data = DataGenerator.generateData(25);

for (const row of data) {
    DateHelper.add(row.start, row.id * 2, 'month', false);
    row.email = `${row.name.toLowerCase().replaceAll(' ', '.')}@example.com`;
}
return data;
};

export const getTimeRows = (employees) => {
    const
        tasks = DataGenerator.generateEvents({ viewStartDate : new Date(), viewEndDate : new Date(), nbrResources : 1, nbrEvents : 35 }).events.map(e => e.name),
        rows  = [];
    let id = 0;

for (const employee of employees) {
    for (let i = 0; i <  Math.ceil(Math.random() * 10); i++) {
        id += 1;
        rows.push({
            id,
            employeeId : employee.id,
            name       : randomGenerator.fromArray(tasks),
            hours      : Math.ceil(Math.random() * 100),
            attested   : Math.random() > 0.5
        });
    }
}
return rows;
};

// These two model classes and two stores uses the built-in data-relations feature to connect related records to each other.
class Employee extends GridRowModel {
    static fields = ['id', 'firstName', 'name', 'start', 'email'];

// Example how to set up a "calculated" field
get unattested() {
    return this.timeRows.reduce((acc, r) => acc + (r.attested ? 0 : 1), 0);
}

// Example how to set up a "calculated" field
get totalTime() {
    return this.timeRows.reduce((acc, r) => acc + r.hours, 0);
}
}

class TimeRow extends GridRowModel {
    static fields = ['id', 'employeeId', 'project', 'hours', 'attested'];

// The relations config, will set up the relationship between the two stores and model classes
static relations = {
    employee : {
        foreignKey            : 'employeeId', // The id of the "other" record
        foreignStore          : 'employeeStore', // Must be set on the record's store
        relatedCollectionName : 'timeRows' // The field in which these records will be accessible from the "other" record
    }
};
}

const employeeStore = new Store({
    modelClass : Employee,
    data       : getEmployees()
});

const timeRowStore = new Store({
    modelClass : TimeRow,
    employeeStore, // Must be set, see relations config above
    data       : getTimeRows(employeeStore.records)
});

const toggleRow = ({ source }) => {
    const
        { record }       = source.cellInfo,
        {  rowExpander } = source.closest('grid').features,
        { name }         = source,
        expandedWidgets  = rowExpander.getExpandedWidgets(record);

if (expandedWidgets) {
    const
        { timeGrid, notesGrid } = expandedWidgets.normal.widgetMap,
        targetGrid = name === 'time' ? timeGrid : notesGrid,
        otherGrid  = name === 'time' ? notesGrid : timeGrid;

if (targetGrid.hidden) {
    targetGrid.hidden = false;
}
else if (otherGrid.hidden) {
    rowExpander.collapse(record);
}
else {
    targetGrid.hidden = true;
}
}
else {
    record._showInitially = name;
    rowExpander.expand(record);
}
};

const grid = new Grid({
    appendTo : 'container',

features : {
rowExpander: {
            column: { hidden: true },
            enableAnimations: true,
            // The widget config declares what type of Widget will be created on each expand
            renderer({ record }) {
              let currentRecord = record;
              const { _showInitially } = record;
              record._showInitially = null;

          return {
            dataset: { id: record.id },
            type: "container",
            layout: "vbox",
            items: {
              timeGrid: {
                hidden: _showInitially !== "time",
                cls: "timerow-grid", // CSS class added to the outer element
                type: "grid",
                autoHeight: true, // Grid resizes to fit all rows
                features: {
                  headerMenu: false,
                  cellEdit: false,
                  cellMenu: false,
                },
                columns: [
                  {
                    field: "branch",
                    text: "branch",
                    editor: false,
                    autoWidth: true,
                  },
                  {
                    field: "Ctrl",
                    text: "Ctrl",
                    editor: false,
                    autoWidth: true,
                  },                      
                ],
                tbar: [
                  {
                    cls: "btn btn-outline-primary btn-sm",
                    type: "button",
                    text: "Show More",
                    onClick: () => {
                      vm.toggleDetailsMore(currentRecord, "", "2");
                    },
                  },
                  {
                    cls: "btn btn-outline-primary btn-sm",
                    type: "button",
                    text: "Show All",
                    onClick: () => {
                      vm.toggleDetailsAll(currentRecord, "", "3");
                    },
                  },
                  "",
                ],
                store: {
                  data: [],
                },
              },
              notesGrid: {
                hidden: _showInitially !== "notes",
                cls: "timerow-grid", // CSS class added to the outer element

                type: "container",
                autoHeight: true,
                items: [
                  {
                    type: "textfield",
                    ref: "txtnotes",
                    cls: "col-md-3",
                    placeholder: "Notes",
                    value: "",
                  },
                  {
                    type: "combo",
                    cls: "col-md-2",
                    ref: "cmbgroup",
                    displayField: "label",
                    items: [],
                    placeholder: "Notes Group",                        
                  },
                  {
                    type: "combo",
                    ref: "cmbuser",
                    cls: "col-md-3",
                    valueField: "label",
                    items: [],
                    placeholder: "Assigned To",
                  },
                  {
                    type: "button",
                    text: "Submit",
                    ref: "btn1",
                    cls: "b-raised b-blue",
                    onClick: function ({ source }) {
                      vm.onNoteSubmit(
                        currentRecord,
                        currentRecord.parentIndex,
                        0,
                        source.closest("container").values,
                        source.closest("container").items[5].store
                      );
                    },
                  },
                  {
                    type: "button",
                    ref: "btn2",
                    text: "Submit & Notify",
                    cls: "b-raised b-green",
                    onClick: ({ source }) => {
                      vm.onNoteSubmit(
                        currentRecord,
                        currentRecord.parentIndex,
                        1,
                        source.closest("container").values,
                        source.closest("container").items[5].store
                      );
                    },
                  },
                  {
                    type: "grid",
                    ref: "notes_grid",
                    cls: "col-md-12",
                    autoHeight: true,
                    features: {
                      headerMenu: false,
                      cellEdit: false,
                      cellMenu: false,
                    },
                    columns: [
                      {
                        text: "Assigned To",
                        field: "NameAssign",
                        autoWidth: true,
                      },
                      {
                        text: "Note",
                        field: "Des",
                        autoWidth: true,
                      },
                      { text: "Added By", field: "Name", autoWidth: true },                          
                    ],
                    store: {
                      data: [],
                    },
                  },
                ],
              },
            },
          };
        },
        listeners: {
          expand: async (source) => {
            try {
              const { record } = source;
              console.log(
                "recordaaaa",
                record,
                record._showInitially,
                record.data._showInitially
              );
              if (record.data._showInitially == "notes") {
                console.log("here we do the api call and bind the data in grid.");
                console.log("Notes Row expanded successfully.");
              } else {
                
                console.log("here we do the api call and bind the data in combos and grid.");
                console.log("Details Row expanded successfully.");
              }
            } catch (error) {
              console.error("Error expanding row:", error);
            }
          },
        },
      },


},

columns : [
    { text : 'Employee no.', field : 'id', width : 100, align : 'center' },
    { text : 'Name', field : 'name', flex : 1 },
    { text : 'Total time', field : 'totalTime', width : 100, align : 'center', editor : false },
    {
        field   : 'show_details',
        text    : '',
        width   : 200,
        default : true,
        action  : true,
        editor  : false,
        type    : 'widget',
        align   : 'center',
        widgets : [
            {
                type    : 'button',
                name    : 'time',
                cls     : 'b-raised',
                color   : 'b-green',
                text    : '',
                onClick : toggleRow
            }
        ],
        renderer : ({ record, widgets, grid }) => {
            const expanded = grid.features.rowExpander.getExpandedWidgets(record)?.normal.widgetMap.timeGrid;

        widgets[0].text = expanded && !expanded.hidden ? 'Hide Details' : 'Show Details';

        if (
            parseInt(record.Matched) === 3 ||
            parseInt(record.Posted) === 1
        ) {
            widgets[0].disabled = true;
        }
        else {
            widgets[0].disabled = false;
        }
    }
},
{
    field   : 'show_detailsnote',
    text    : '',
    width   : 200,
    default : true,
    action  : true,
    editor  : false,
    type    : 'widget',
    align   : 'center',
    widgets : [
        {
            type    : 'button',
            cls     : 'b-raised',
            color   : 'b-green',
            name    : 'notes',
            text    : '',
            onClick : toggleRow
        }
    ],
    renderer : ({ record, widgets, grid }) => {
        const expanded = grid.features.rowExpander.getExpandedWidgets(record)?.normal.widgetMap.notesGrid;

        widgets[0].text = expanded && !expanded.hidden ? 'Hide Note' : 'Show Note';
    }
}
],

store : employeeStore
});


You can put the same code in the example https://bryntum.com/products/grid/examples/nested-grid/ and see that it will only do a console log for the button clicked first, which means the expander calls only once on the button click. If the Details button is clicked first then it will log a message for that else it will log for the notes grid.

issue screenshot
issue screenshot
Screenshot 2024-02-16 095234.jpg (559.31 KiB) Viewed 93 times

I hope the problem statement is clear and we're able to explain it well.

Many thanks for the support provided so far.


Post by joakim.l »

Hi

Yes, that is what the code does. If the row is already expanded, it can't do it again, so it just toggles the visibility of the expanded widgets. You should probably add your data binding in the toggleRow function instead.

Regards
Joakim


Post Reply