Generic FlexGrid with CellFactory

Posted by: pascal.hanel on 13 September 2020, 6:51 pm EST

  • Posted 13 September 2020, 6:51 pm EST

    Hi all,

    I am currently working on a quite complex Angular 10 application with many FlexGrids, which should be generated from some meta information coming from the backend. This means, the cells of the grid should display ComboBoxes, Autosuggestions, depending on the meta data coming from the backend.

    I have implemented all of this with ng-templates (+ wjFlexGridCellTemplate) and inside of those ng-templates I used ng-containers with ngSwitch to select the right template depending on the meta data (if it should be a dropdown, autosuggestion and so on…)

    Unfortunately, rendering takes quite some time with those complex checks inside of the ng-template. Therefore I started using a CustomCellFactory for my requirement.

    This is how my updateCell function looks like:

    
    public updateCell(p: GridPanel, r: number, c: number, cell: HTMLElement, rng?: CellRange, updateContent?: boolean) {
      switch (p.cellType) {
        // regular cells
        case CellType.Cell:
          // get cell geometry
          super.updateCell(p, r, c, cell, rng, false);
    
          switch (dataType) {
            case 'dropDown':
              this.createGenericDropdown(p, r, c, cell);
              break;
    
            case 'string':
              this.createDefaultCell(p, r, c, cell);
              break;
    
            default:
              break;
          }
    
          break;
    
          ...
        }
      }
    
    

    And then for example, creating the generic Dropdowns:

    
      public createGenericDropdown(panel: GridPanel, rowIdx: number, colIdx: number, cell: HTMLElement): void {
        const supplierId = this.headerDefinitions[colIdx].supplierId;
        const itemsSource = panel.grid.itemsSource;
        const combo = new ComboBox(document.createElement('div'), {
          displayMemberPath: 'name',
          selectedValuePath: 'name',
          selectedValue: itemsSource[rowIdx].map[supplierId],
          itemsSource: itemsSource[rowIdx].propertyMeta[supplierId].dropDownValues
        });
        cell.innerHTML = '';
        cell.appendChild(combo.hostElement);
        panel.rows[rowIdx].height = 36;
      }
    
    

    The rendering already seems to be a bit faster, but now I am a bit stuck with handling the editing of the grid. In the ng-templates I used the (lostFocus) event to trigger some save action. And again, depending on some further meta information I had to use different save actions in the component where I have implemented the FlexGrid.

    Now coming to my questions:

    1. How can I bind a function from the ComboBox lostFocus event inside of another component?
    2. Instead of using a ComboBox, I would prefer using a DataMap. But I noticed that binding a DataMap to a Column in the updateCell function is causing a re-rendering loop of the cell.

    Do you have any other examples of more extended CellFactory usage than the demo for the CustomCellFactory?

    As it doesn’t even show how the editing works with custom cell factories, it is a bit too basic in my opinion.

    Thank you

    Pascal

  • Posted 14 September 2020, 3:12 pm EST

    Hi Pascal,

    We are sorry but we do not have any sample that demonstrates how to bind input controls to FlexGrid using CellFactory. The CellFactory is used for customizing the cells in the FlexGrid only and is rarely used for adding custom editors in the FlexGrid.

    Regarding the lostFocus event, we understand that you were using the lostFocus event in the cell template to save the values but can you let us know how you added the event handler for this event in a different component. Were you using the EventEmitter object?

    Since you need to add the handler in a different component, you will need to get the reference of the ComboBox or other controls in the component. You can use the static getControl method of the Control class:

    public addSaveHandler(grid, row, col) {
    	// get the cell element
    	var cell = grid.cells.getCellElement(row, col);
    	// a cell will only be created if it is actually displayed on the DOM
    	// if it is not displayed, it will be null
    	if(cell) {
    		var ctlHost = cell.querySelector('.wj-control');
    		var ctl = wijmo.Control.getControl(ctlHost);
    		if(ctl) {
    			ctl.lostFocus.addHandler(...);
    		}
    	}
    }
    

    Let us know if this works for you or otherwise.

    Regarding the datamap, setting the DataMap of the column will always refresh the grid and therefore the updateCell method will be called again. Also, the DataMap works on column level and with the same itemsSource but in your case, each row has a different itemsSource so we would not suggest you use DataMaps for your requirements.

    We hope this helps.

    Regards,

    Ashwin

  • Posted 23 September 2020, 4:33 pm EST

    Hi Ashwin,

    thanks for you reply. I found a way of handling the editing by raising the cellEditEnded event of my grid. Then I can handle the saving of the data in the cellEditEnded method of the grid, where the custom element is used in.

      public createGenericDropdown(panel: GridPanel, rowIdx: number, colIdx: number, cell: HTMLElement, rng: CellRange): void {
        const supplierId = this.headerDefinitions[colIdx].supplierId;
        const itemsSource = panel.grid.itemsSource;
        const combo = new ComboBox(document.createElement('div'), {
          displayMemberPath: 'name',
          selectedValuePath: 'name',
          selectedValue: itemsSource[rowIdx].map[supplierId],
          itemsSource: itemsSource[rowIdx].propertyMeta[supplierId].dropDownValues,
          text: itemsSource[rowIdx].map[supplierId],
          lostFocus: (s, e) => {
            panel.grid.beginUpdate();
            const cellRange = new CellRange(rowIdx, colIdx);
            const customEvent = new CellRangeEventArgs(panel, cellRange);
            panel.setCellData(rowIdx, colIdx, combo.selectedValue, false, true);
            panel.grid.cellEditEnded.raise(panel.grid, customEvent);
            panel.grid.endUpdate();
          }
        });
        cell.innerHTML = '';
        cell.appendChild(combo.hostElement);
        panel.rows[rowIdx].height = 36;
      }
    

    I got the point with DataMaps, but unfortunately this is still one of my main requirements. I need custom editors for several rows (not columns) as I am using data in a similar way as the transposed grid.

    Is it possible to use the new property “editor” within the CellFactory to assign my own editors to rows? Or will this also trigger the change detection and end up in a loop?

    Would really appreciate any other idea about how to have a good performance when using row-dependend custom editors without the usage of the grid itemFormatter and also without ng-templates and wjFlexGridCellTemplate. Both solutions are not satisfying with their performance in huge grids.

    Thank you

    Pascal

  • Posted 27 September 2020, 4:27 pm EST

    Hi,

    We are working on this and will provide you an update soon.

    Regards

  • Posted 27 September 2020, 7:36 pm EST

    Hi Pascal,

    I am extremely sorry for the delayed response.

    Unfortunately, editor property also works on the column level so this will also not fulfill your requirements properly. You will need to manually add custom editors with different itemsSource as you are doing in the updateCell method. If you will not, in any way, refresh the grid, then no loop will be created. For example, if you are just appending a new element, then a loop will not be created. But if you are updating the data in the flexgrid, or updating some properties of the row or column, then this method will be called again, creating a loop.

    There may be another way for creating data maps with different itemsSource. Instead of adding the datamap on the column, you can add it to the row of the flexgrid:

    flexgrid.rows[0].dataMap = new DataMap(....);
    

    But this will add the data map on all the cells in the row just like a column does for its cells.

    You can also create dynamic datamaps where you assign all the data as the source of the datamap and then filter out the items according to the row. Refer to the sample link below for reference:

    https://www.grapecity.com/wijmo/demos/Grid/Columns/DynamicDataMaps/angular

    In this sample, the source of the City datamap changes when the value of the Country column changes. But, for this, you will need to modify the structure of your data source so that all the itemsSource of each different rows are stored in a single array. If you could update you data like this, then this solution will be easier for you to implement.

    Regarding your own editors:

    You can also add your custom editor’s the same way as any wijmo controls. Create an element and create an editor on that element add it inside the cell of the FlexGrid. This will not trigger any loop because modifying the cell element does not refresh the grid.

    I hope this helps.

    ~regards

Need extra support?

Upgrade your support plan and get personal unlimited phone support with our customer engagement team

Learn More

Forum Channels