How do I populate a column (cell) with a DropDownList?

1 Answer 56 Views
DropDownList Grid
Doug
Top achievements
Rank 2
Iron
Iron
Iron
Doug asked on 19 Aug 2024, 02:59 PM | edited on 19 Aug 2024, 03:01 PM

'use client'

import { ReactElement, useMemo, useRef, useState } from "react"
import dynamic from "next/dynamic"

import { GridColumn as Column, GridCellProps, GridItemChangeEvent, GridRowProps, GridToolbar } from "@progress/kendo-react-grid"

import { CompositeFilterDescriptor } from "@progress/kendo-data-query"
import { filterBy } from "@progress/kendo-react-data-tools"

import { CellRender, RowRender } from "../../Other/renderers"
const Grid: any = dynamic(() => import("@progress/kendo-react-grid").then(module => module.Grid as any), { ssr: false })

import { process } from '@progress/kendo-data-query'
import useCdpGridFunctions from "./localHooks/useCdpGridFunctions"

import addIdProperty from './helpers/addIdProperty' // Helpers
import formatDates from './helpers/formatDates'

import './CDPGrid.css'

import fillComboDynamic from "../../fillComboDynamic"

const EDIT_FIELD = 'inEdit'; const GridColumns = [{ field: '' }]


const CDPExampleComponent = ({ cdpData, data, setData, selectedGrid, selectedFilter, basicGridName, tableHmy, databasePackage, gridProps }) => {

  // console.log('basicGridName', basicGridName)
  
  async function getDbGridConfig() {
    // const controlsUrl = `/api/storedProcedures/${paramSelectedGrid}/controls`
    const controlsUrl = `/api/storedProcedures/rofoGridConfig?grid=${basicGridName}`
    // return await (await fetch(controlsUrl)).json()
    const columns = await fetch(controlsUrl)
    return columns
  }
  const [reactiveGridColumns, setReactiveGridColumns] = useState()
  
  useMemo(() => {
    const gridColumns = getDbGridConfig()
    .then((response) => {return response.json()})
    .then((data) => {
      console.log('columns', data)
      setReactiveGridColumns(data)
    })
  }, [])

  // console.log('data', data)

  // const result = filterBy(data, { filter: selectedFilter })

  const filteredData = process(data, { filter: selectedFilter })
  // console.log('filteredData', filteredData)

  data = addIdProperty(filteredData.data) // Data that's sent to the Grid
  data = formatDates(data)

  const [changes, setChanges] = useState<boolean>(false)
  const [columns, setColumns] = useState(GridColumns)

  useCdpGridFunctions(cdpData, setColumns)

  const itemChange = (event: GridItemChangeEvent) => {
    let field = event.field || ''
    event.dataItem[field] = event.value
    let newData = data.map((item: any) => {
      if (item.Id === event.dataItem.Id) {
        item[field] = event.value
      }
      return item
    })
    setData(newData)
    setChanges(true)
  }
  const editedDataItem = useRef()

  // enterEdit ////////////////////////////////////////////////////////////////////
  const enterEdit: any = (dataItem: any, field: string) => {

    // console.log('dataItem', dataItem)
    // console.log('field', field)

    const newData = data.map((item: any) => ({ ...item, [EDIT_FIELD]: item.Id === dataItem.Id ? field : undefined }))

    // console.log('newData', newData)
    
    setData(newData)
    newData['field'] = field
    editedDataItem.current = newData[dataItem.Id]

    // console.log("enterEdit | newData")
    // console.log('editedDataItem.current', editedDataItem.current)
    // console.log(`Row: ${dataItem.Id}, Field: ${field}`, dataItem)
  }

  // exitEdit /////////////////////////////////////////////////////////////////////
  const exitEdit: any = () => {
    // console.log('exitEdit')
    // const newData = data.map((item: any) => ({ ...item, [EDIT_FIELD]: undefined }))
    const newData = data.map((item: any) => ({ ...item, [EDIT_FIELD]: undefined }))
    // console.log('EDIT_FIELD', newData.EDIT_FIELD)
    setData(newData)
    // console.log("exitEdit | newData", newData)
    // console.log('editedDataItem', editedDataItem.current)
  }

  // TODO: I need the title here
  const saveChanges = () => {

    // console.log('Save Changes')
    const dataSegment = data.splice(0, data.length, ...data) // Giant Array

    setChanges(false)
    
    // console.log('Updating the database'); // TODO: Update the database correctly
    updateDatabase(databasePackage, editedDataItem.current)

  }

  const updateDatabase = async (databasePackage: any, edits: any) => { // TODO: Get the stored procedure to work correctly
    const storedProcedureParameters = {
      tableHmy: edits[databasePackage.current.tableHmy],
      amendmentid: edits.AmendmentID,
      fieldToUpdate: edits.inEdit,
      fieldValue: edits[edits.inEdit],
      domainUser: databasePackage.current.domainUser,
      grid: databasePackage.current.basicGridName
    }

    fetch('/api/storedProcedures/[storedProcedure]', {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json'
      },
      body: JSON.stringify(storedProcedureParameters),
    })
      .then((response) => {
        // console.log('response', response)
        return response.json()
      })
      .then((data) => {
        // console.log('data', data)
      })
  }

  const cancelChanges = () => {
    setData(data)
    setChanges(false)
  }

  const customCellRender: any = (td: ReactElement<HTMLTableCellElement>, props: GridCellProps) => {
    return <CellRender originalProps={props} td={td} enterEdit={enterEdit} editField={EDIT_FIELD} />
  }  // Error, until it returns the correct type, an empty render function returns no data

  const customRowRender: any = (tr: ReactElement<HTMLTableRowElement>, props: GridRowProps) => {
    return <RowRender originalProps={props} tr={tr} exitEdit={exitEdit} editField={EDIT_FIELD} />
  } // Error, until it returns the correct type, an empty render function returns no data; params display column-names

  function dontDisplay(column, value) {
    let displayStatus = true
    value.map((hiddenElement: any) => {
      if (column === hiddenElement) {
        // console.log(`column: ${column} | hiddenElement: ${hiddenElement}`)
        displayStatus = false
      }
    })
    return displayStatus
  }

  function permitEdits(column:any, fields:any) {
    const permittedFields = fields.filter((field:any, index:number) => {
      if (field.ReadOnly !== true) {
        // console.log('field.ReadOnly', field.Name, field.ReadOnly)
        return fields[index]
      }
    })

    try {
      let permitStatus = true
      permittedFields.map((permittedElement: any) => {
        if (column === permittedElement.Name) {
          permitStatus = false
        }
      })
      return permitStatus
    }
    catch (error) {
      return false
    }
  }

  return (
    <>
      {/* <h1>CDP Grid (E2) {selectedGrid}</h1> */}
      <Grid
        id='mainGrid'
        // ref={gridRef}
        style={{ height: '82vh' }} // Grid Height, up from 70vh
        data={data}
        dataItemKey={'Id'}
        // dataItemKey={`${tableHmy}`}
        rowHeight={50}
        resizable={true}
        reorderable={true}
        onItemChange={itemChange} cellRender={customCellRender} rowRender={customRowRender} editField={EDIT_FIELD}>
        {/* {
          gridProps.columnProps.map((column:any, index:any) => {
            if (!dontDisplay(column.field, ['inEdit', 'Id'])) { }
            else if (column.ReadOnly) { // Enable Editables
              return <Column key={index} {...column} format="{0:d}" editable={false} field={column.FieldName} width={column.Width} title={column.HeaderText} />
              }
              else {
                return <Column key={index} {...column} editable={true} className={'azureColor'} field={column.FieldName} width={column.Width} title={column.HeaderText} />
            }
          })
        } */}
        {
          reactiveGridColumns?.map((column:any, index:any) => {

            // console.log('column', column)

            if (column.IsHidden === 1) {}
            else if (column.DropDownProc !== null) {
              // fillComboDynamic(column)

              // How do I replace the field={column.DBName} text with a DropDownList?
              return <Column key={index} {...column} editable={true} className={'fillableDropdown'} field={column.DBName} width={column.Width} title={column.FriendlyName} />
            }
            else if (column.IsEditable !== 0) {
              return <Column key={index} {...column} editable={true} className={'azureColor'} field={column.DBName} width={column.Width} title={column.FriendlyName} />
            }
            else {
              return <Column key={index} {...column} format="{0:d}" editable={false} field={column.DBName} width={column.Width} title={column.FriendlyName} />
            }

            // return <Column key={index} {...column} editable={true} className={'azureColor'} field={column.DBName} width={column.Width} title={column.FriendlyName} />

          })
        }
        <GridToolbar>
          <button
            title='Save Changes'
            className='k-button k-button-md k-rounded-md k-button-solid k-button-solid-base'
            onClick={saveChanges}
            disabled={!changes}>Save Changes</button>
          <button
            title='Cancel Changes'
            className='k-button k-button-md k-rounded-md k-button-solid k-button-solid-base'
            // onClick={cancelChanges}
            disabled={!changes}>Cancel Changes</button>
        </GridToolbar>
      </Grid>
    </>
  )
}

export default CDPExampleComponent

Doug
Top achievements
Rank 2
Iron
Iron
Iron
commented on 19 Aug 2024, 03:05 PM

This Example is very close to what I'm trying to do. Regrettably, I arrived on the React scene AFTER the functional approach became prevalent. It's taking all day to figure out what's going on with this Class-Based example.
Doug
Top achievements
Rank 2
Iron
Iron
Iron
commented on 19 Aug 2024, 04:09 PM | edited

I've made some progress! The custom cell renderer contains a "dataItem" property that need to work with...

The dataItem must be replaced with my DropDownList - but how?

'use client'

import { ReactElement, useMemo, useRef, useState } from "react"
import dynamic from "next/dynamic"

import { GridColumn as Column, GridCellProps, GridItemChangeEvent, GridRowProps, GridToolbar } from "@progress/kendo-react-grid"

import { CompositeFilterDescriptor } from "@progress/kendo-data-query"
import { filterBy } from "@progress/kendo-react-data-tools"

import { CellRender, RowRender } from "../../Other/renderers"
const Grid: any = dynamic(() => import("@progress/kendo-react-grid").then(module => module.Grid as any), { ssr: false })

import { process } from '@progress/kendo-data-query'
import useCdpGridFunctions from "./localHooks/useCdpGridFunctions"

import addIdProperty from './helpers/addIdProperty' // Helpers
import formatDates from './helpers/formatDates'

import './CDPGrid.css'

const EDIT_FIELD = 'inEdit'; const GridColumns = [{ field: '' }]

const CDPExampleComponent = ({ cdpData, data, setData, selectedGrid, selectedFilter, basicGridName, tableHmy, databasePackage, gridProps }) => {
  
  async function getDbGridConfig() {
    const controlsUrl = `/api/storedProcedures/rofoGridConfig?grid=${basicGridName}`
    const columns = await fetch(controlsUrl)
    return columns
  }
  const [reactiveGridColumns, setReactiveGridColumns] = useState()
  
  useMemo(() => {
    const gridColumns = getDbGridConfig()
    .then((response) => {return response.json()})
    .then((data) => {
      console.log('columns', data)
      setReactiveGridColumns(data)
    })
  }, [])

  const filteredData = process(data, { filter: selectedFilter })

  data = addIdProperty(filteredData.data) // Data that's sent to the Grid
  data = formatDates(data)

  const [changes, setChanges] = useState<boolean>(false)
  const [columns, setColumns] = useState(GridColumns)

  useCdpGridFunctions(cdpData, setColumns)

  const itemChange = (event: GridItemChangeEvent) => {
    let field = event.field || ''
    event.dataItem[field] = event.value
    let newData = data.map((item: any) => {
      if (item.Id === event.dataItem.Id) {
        item[field] = event.value
      }
      return item
    })
    setData(newData)
    setChanges(true)
  }
  const editedDataItem = useRef()

  // enterEdit ////////////////////////////////////////////////////////////////////
  const enterEdit: any = (dataItem: any, field: string) => {
    const newData = data.map((item: any) => ({ ...item, [EDIT_FIELD]: item.Id === dataItem.Id ? field : undefined }))
    setData(newData)
    newData['field'] = field
    editedDataItem.current = newData[dataItem.Id]
  }

  // exitEdit /////////////////////////////////////////////////////////////////////
  const exitEdit: any = () => {
    const newData = data.map((item: any) => ({ ...item, [EDIT_FIELD]: undefined }))
    setData(newData)
  }

  const saveChanges = () => { // TODO: I need the title here
    const dataSegment = data.splice(0, data.length, ...data) // Giant Array
    setChanges(false)
    updateDatabase(databasePackage, editedDataItem.current)
  }

  const updateDatabase = async (databasePackage: any, edits: any) => { // TODO: Get the stored procedure to work correctly
    const storedProcedureParameters = {
      tableHmy: edits[databasePackage.current.tableHmy],
      amendmentid: edits.AmendmentID,
      fieldToUpdate: edits.inEdit,
      fieldValue: edits[edits.inEdit],
      domainUser: databasePackage.current.domainUser,
      grid: databasePackage.current.basicGridName
    }

    fetch('/api/storedProcedures/[stored_procedure]', {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json'
      },
      body: JSON.stringify(storedProcedureParameters),
    })
      .then((response) => {
        return response.json()
      })
      .then((data) => {
      })
  }

  const cancelChanges = () => {
    setData(data)
    setChanges(false)
  }

  const customCellRender: any = (td: ReactElement<HTMLTableCellElement>, props: GridCellProps) => { // Cell Data

    // TODO: Current Development: Get the DaysType column key (data) and replace it with a DropDownList
    // console.log('dataItem', props.dataItem)
    console.log('dataItem', props.dataItem.DaysType)

    return <CellRender originalProps={props} td={td} enterEdit={enterEdit} editField={EDIT_FIELD} />
  }  // Error, until it returns the correct type, an empty render function returns no data

  const customRowRender: any = (tr: ReactElement<HTMLTableRowElement>, props: GridRowProps) => {
    return <RowRender originalProps={props} tr={tr} exitEdit={exitEdit} editField={EDIT_FIELD} />
  } // Error, until it returns the correct type, an empty render function returns no data; params display column-names

  return (
    <>
      <Grid
        id='mainGrid'
        style={{ height: '82vh' }} // Grid Height, up from 70vh
        data={data}
        dataItemKey={'Id'}
        rowHeight={50}
        resizable={true}
        reorderable={true}
        onItemChange={itemChange} cellRender={customCellRender} rowRender={customRowRender} editField={EDIT_FIELD}>
        {
          reactiveGridColumns?.map((column:any, index:any) => {
            // console.log('column', column)
            if (column.IsHidden === 1) {}
            else if (column.DropDownProc !== null) {
              return <Column key={index} {...column} editable={true} className={'fillableDropdown'} field={column.DBName} width={column.Width} title={column.FriendlyName} />
            }
            else if (column.IsEditable !== 0) {
              return <Column key={index} {...column} editable={true} className={'azureColor'} field={column.DBName} width={column.Width} title={column.FriendlyName} />
            }
            else {
              return <Column key={index} {...column} format="{0:d}" editable={false} field={column.DBName} width={column.Width} title={column.FriendlyName} />
            }
            // return <Column key={index} {...column} editable={true} className={'azureColor'} field={column.DBName} width={column.Width} title={column.FriendlyName} />
          })
        }
        <GridToolbar>
          <button
            title='Save Changes'
            className='k-button k-button-md k-rounded-md k-button-solid k-button-solid-base'
            onClick={saveChanges}
            disabled={!changes}>Save Changes</button>
          <button
            title='Cancel Changes'
            className='k-button k-button-md k-rounded-md k-button-solid k-button-solid-base'
            // onClick={cancelChanges}
            disabled={!changes}>Cancel Changes</button>
        </GridToolbar>
      </Grid>
    </>
  )
}

export default CDPExampleComponent


Doug
Top achievements
Rank 2
Iron
Iron
Iron
commented on 19 Aug 2024, 07:58 PM

Almost there...how do I relate the cell.dataItem prop to its parent column?
Doug
Top achievements
Rank 2
Iron
Iron
Iron
commented on 20 Aug 2024, 09:19 PM | edited

How do I separate the Dropdowns?

The "Days Type" column contains the ROFO controls, which belong in the "ROFO/ROFR" column instead.

'use client'

import { ReactElement, useMemo, useRef, useState } from "react"
import dynamic from "next/dynamic"

import { GridColumn as Column, GridCellProps, GridItemChangeEvent, GridRowProps, GridToolbar } from "@progress/kendo-react-grid"

import { CompositeFilterDescriptor } from "@progress/kendo-data-query"
import { filterBy } from "@progress/kendo-react-data-tools"

import { CellRender, RowRender } from "../../Other/renderers"
const Grid: any = dynamic(() => import("@progress/kendo-react-grid").then(module => module.Grid as any), { ssr: false })

import { process } from '@progress/kendo-data-query'
import useCdpGridFunctions from "./localHooks/useCdpGridFunctions"

import addIdProperty from './helpers/addIdProperty' // Helpers
import formatDates from './helpers/formatDates'

import './CDPGrid.css'
import { DropDownList } from "@progress/kendo-react-dropdowns"

const EDIT_FIELD = 'inEdit'; const GridColumns = [{ field: '' }]

const CDPExampleComponent = ({ cdpData, data, setData, selectedGrid, selectedFilter, basicGridName, tableHmy, databasePackage, gridProps }) => {
  
  async function getDbGridConfig() {
    const controlsUrl = `/api/storedProcedures/rofoGridConfig?grid=${basicGridName}`
    const columns = await fetch(controlsUrl)
    return columns
  }
  const [reactiveGridColumns, setReactiveGridColumns] = useState()
  
  useMemo(() => {
    const gridColumns = getDbGridConfig()
    .then((response) => {return response.json()})
    .then((data) => {
      console.log('columns', data)
      setReactiveGridColumns(data)
    })
  }, [])

  const filteredData = process(data, { filter: selectedFilter })

  data = addIdProperty(filteredData.data) // Data that's sent to the Grid
  data = formatDates(data)

  const [changes, setChanges] = useState<boolean>(false)
  const [columns, setColumns] = useState(GridColumns)

  useCdpGridFunctions(cdpData, setColumns)

  const itemChange = (event: GridItemChangeEvent) => {
    let field = event.field || ''
    event.dataItem[field] = event.value
    let newData = data.map((item: any) => {
      if (item.Id === event.dataItem.Id) {
        item[field] = event.value
      }
      return item
    })
    setData(newData)
    setChanges(true)
  }
  const editedDataItem = useRef()

  // enterEdit ////////////////////////////////////////////////////////////////////
  const enterEdit: any = (dataItem: any, field: string) => {
    const newData = data.map((item: any) => ({ ...item, [EDIT_FIELD]: item.Id === dataItem.Id ? field : undefined }))
    setData(newData)
    newData['field'] = field
    editedDataItem.current = newData[dataItem.Id]
  }

  // exitEdit /////////////////////////////////////////////////////////////////////
  const exitEdit: any = () => {
    const newData = data.map((item: any) => ({ ...item, [EDIT_FIELD]: undefined }))
    setData(newData)
  }

  const saveChanges = () => { // TODO: I need the title here
    const dataSegment = data.splice(0, data.length, ...data) // Giant Array
    setChanges(false)
    updateDatabase(databasePackage, editedDataItem.current)
  }

  const updateDatabase = async (databasePackage: any, edits: any) => { // TODO: Get the stored procedure to work correctly
    const storedProcedureParameters = {
      tableHmy: edits[databasePackage.current.tableHmy],
      amendmentid: edits.AmendmentID,
      fieldToUpdate: edits.inEdit,
      fieldValue: edits[edits.inEdit],
      domainUser: databasePackage.current.domainUser,
      grid: databasePackage.current.basicGridName
    }

    fetch('/api/storedProcedures/[storedProcedure]', {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json'
      },
      body: JSON.stringify(storedProcedureParameters),
    })
      .then((response) => {
        return response.json()
      })
      .then((data) => {
      })
  }

  const cancelChanges = () => {
    setData(data)
    setChanges(false)
  }

  function presentCustomCellData(props:any, td:any) {

    const bridgeIndex = props.columnIndex + 1

    const customCellBridge = {
      column: reactiveGridColumns[bridgeIndex].DBName, // State variable from line 32
      dropdownStatus: reactiveGridColumns[bridgeIndex].DropDownProc,
      cellProps: props,
      colmProps: reactiveGridColumns
    }

    console.log('customCellBridge', customCellBridge)
    
    if (customCellBridge.dropdownStatus !== null && customCellBridge.column == 'DaysType') {
      return (<DropDownList value={customCellBridge.cellProps.dataItem[customCellBridge.column]}></DropDownList>)
    }

    if (customCellBridge.dropdownStatus !== null && customCellBridge.column == 'ROFOROFRType') {
      return (<DropDownList value={customCellBridge.cellProps.dataItem[customCellBridge.column]}></DropDownList>)
    }
    
    return <CellRender originalProps={props} td={td} enterEdit={enterEdit} editField={EDIT_FIELD} />
  }

  const customCellRender: any = (td: ReactElement<HTMLTableCellElement>, props: GridCellProps) => { // Cell Data
    return presentCustomCellData(props, td)
  }  // Error, until it returns the correct type, an empty render function returns no data

  const customRowRender: any = (tr: ReactElement<HTMLTableRowElement>, props: GridRowProps) => {
    return <RowRender originalProps={props} tr={tr} exitEdit={exitEdit} editField={EDIT_FIELD} />
  } // Error, until it returns the correct type, an empty render function returns no data; params display column-names

  return (
    <>
      <Grid
        id='mainGrid'
        style={{ height: '82vh' }} // Grid Height, up from 70vh
        data={data}
        dataItemKey={'Id'}
        rowHeight={50}
        resizable={true}
        reorderable={true}
        onItemChange={itemChange} cellRender={customCellRender} rowRender={customRowRender} editField={EDIT_FIELD}>
        {
          reactiveGridColumns?.map((column:any, index:any) => {

            console.log('column', column)

            if (column.IsHidden === 1) {}
            else if (column.DropDownProc !== null) {
              return <Column key={index} {...column} editable={true} className={'fillableDropdown'} field={column.DBName} width={column.Width} title={column.FriendlyName} />
            }
            else if (column.IsEditable !== 0) {
              return <Column key={index} {...column} editable={true} className={'azureColor'} field={column.DBName} width={column.Width} title={column.FriendlyName} />
            }
            else {
              return <Column key={index} {...column} format="{0:d}" editable={false} field={column.DBName} width={column.Width} title={column.FriendlyName} />
            }
            // return <Column key={index} {...column} editable={true} className={'azureColor'} field={column.DBName} width={column.Width} title={column.FriendlyName} />
          })
        }
        <GridToolbar>
          <button
            title='Save Changes'
            className='k-button k-button-md k-rounded-md k-button-solid k-button-solid-base'
            onClick={saveChanges}
            disabled={!changes}>Save Changes</button>
          <button
            title='Cancel Changes'
            className='k-button k-button-md k-rounded-md k-button-solid k-button-solid-base'
            // onClick={cancelChanges}
            disabled={!changes}>Cancel Changes</button>
        </GridToolbar>
      </Grid>
    </>
  )
}

export default CDPExampleComponent

1 Answer, 1 is accepted

Sort by
1
Yanko
Telerik team
answered on 21 Aug 2024, 02:01 PM

Hi, Doug,

The example provided in your initial post with the class component is outdated. Here is an updated version of that particular example with functional components:

On a side note, the used cellRender functionality is called for every cell in the grid. To have more control over the column in which the cell is rendered, I can suggest utilizing the new cells property of the grid as it can be applied to a specific part of the grid such as headers, footers, or a data column, and set custom cell implementations only where needed. You can read more about how to use custom cells in this article from our documentation:

For convenience, I prepared a demo for you that displays a custom DropDownCell in a grid that contains two different dropdowns. The two columns use the same cell and the custom cell displays a different dropdowns in each column based on a conditional statement, in that case, it is the field title:

Is this the scenario that you are trying to achieve? Please give it a try and let me know if I can help you further with this matter.

Regards,
Yanko
Progress Telerik

Do you have a stake in the designеr-developer collaboration process? If so, take our survey to share your perspective and become part of this global research. You’ll be among the first to know once the results are out.
-> Start The State of Designer-Developer Collaboration Survey 2024

Tags
DropDownList Grid
Asked by
Doug
Top achievements
Rank 2
Iron
Iron
Iron
Answers by
Yanko
Telerik team
Share this question
or