react-leaflet , when a country is selected I want to highlight that country borders - leaflet

I am trying to use geoJson layer and pass it the selectedCountry .
I get this error : "BordersGeoJSON.js:8 Uncaught TypeError: Cannot read properties of undefined (reading 'geoJSON')"
I have checked that selectedCountry is a valid geoJSON object. The console log looks like this:
{type: 'Feature', properties: {…}, geometry: {…}}
geometry
:
{type: 'Polygon', coordinates: Array(1)}
properties
:
{name: 'Ireland', iso_a2: 'IE', iso_a3: 'IRL', iso_n3: '372'}
type
:
"Feature"
[[Prototype]]
:
Object
My code is this:
import { useEffect } from 'react';
import { L } from 'leaflet';
const BordersGeoJSON = ({mapRef, selectedCountry}) => {
useEffect(() => {
if (mapRef && selectedCountry) {
console.log(selectedCountry)
const layer = L.geoJSON(selectedCountry, {
style: () => ({ color: "red" }),
});
layer.addTo(mapRef.leafletElement);
}
}, [mapRef, selectedCountry]);
return null;
};
export default BordersGeoJSON
I have tried to use selectedCountry geometry only but I get the same error
I have tried using directly the geoJSON file instead of selectedCountry but I get the same error
Here it is on sandbox : https://codesandbox.io/s/vigilant-galois-7y1xd3?file=/src/components/leaflet/BasicMap/PointsOfInterest.js

I got it working.
I have changed things to use GeoJson component and use the selectedCountry state. GeoJson needs a key to update every time selectedCountry changes .
here is the code :
import { useEffect, useState } from 'react';
import {GeoJSON } from 'react-leaflet';
const BordersGeoJSON = ({bounds, selectedCountry}) => {
const [geoJsonKey, addToGeoJsonKey] = useState(1);
useEffect(() => {
addToGeoJsonKey(geoJsonKey + 1)
}, [selectedCountry])
return (
<>
{ selectedCountry &&
<GeoJSON
key={geoJsonKey}
data={selectedCountry}
style={() => ({ color: 'blue' })}
/>
}
</>
);
};
export default BordersGeoJSON

Related

Uncaught (in promise) TypeError: hobbies is not iterable at createHobby when using Prisma and Postgresql

So I'm very new to Prisma, and actually also to React. My Postgresql database works, but I'm trying to show the stored data in my application. My very simple table in the schema file looks like this:
model Hobby {
id Int #id #default(autoincrement())
title String
}
I'm using useContext to distribute my createHobby functionality, this is what the context file looks like.
export async function getServerSideProps() {
const hobbies: Prisma.HobbyUncheckedCreateInput[] = await prisma.hobby.findMany();
return {
props: {initialHobbies: hobbies},
};
}
export const HobbyContext = createContext({})
function Provider({ children, initialHobbies }){
const [hobbies, setHobbies] = useState<Prisma.HobbyUncheckedCreateInput[]>(initialHobbies);
const createHobby = async (title) => {
const body: Prisma.HobbyCreateInput = {
title,
};
await fetcher("/api/create-hobby", {hobby : body});
console.log(hobbies);
const updatedHobbies = [
...hobbies,
body
];
setHobbies(updatedHobbies);
const contextData = {
hobbies,
createHobby,
}
return (
<HobbyContext.Provider value={contextData}>
{children}
</HobbyContext.Provider>
);
};
export default HobbyContext;
export {Provider};
Here I get the following error Uncaught (in promise) TypeError: hobbies is not iterable at createHobby. Which refers to the const updatedHobbies = [...hobbies, body];
For more context, I have a HobbyCreate.tsx which creates a little hobby card that renders the title of the hobby, which is submitted with a form.
function HobbyCreate({updateModalState}) {
const [title, setTitle] = useState('');
const {createHobby} = useHobbiesContext();
const handleChange = (event) => {
setTitle(event.target.value)
};
const handleSubmit = (event) => {
event.preventDefault();
createHobby(title);
};
return (
...
<form onSubmit={handleSubmit}></form>
...
)
I can't really figure out what is going wrong, I assume somewhere when creating the const [hobbies, setHobbies] and using the initialHobbies.
I don't think you're using the Context API correctly. I've written working code to try and show you how to use it.
Fully typed hobby provider implementation
This is a fully typed implementation of your Provider:
import { createContext, useState } from 'react';
import type { Prisma } from '#prisma/client';
import fetcher from 'path/to/fetcher';
export type HobbyContextData = {
hobbies: Prisma.HobbyCreateInput[]
createHobby: (title: string) => void
};
// you could provide a meaningful default value here (instead of {})
const HobbyContext = createContext<HobbyContextData>({} as any);
export type HobbyProviderProps = React.PropsWithChildren<{
initialHobbies: Prisma.HobbyCreateInput[]
}>;
function HobbyProvider({ initialHobbies, children }: HobbyProviderProps) {
const [hobbies, setHobbies] = useState<Prisma.HobbyCreateInput[]>(initialHobbies);
const createHobby = async (title: string) => {
const newHobby: Prisma.HobbyCreateInput = {
title,
};
await fetcher("/api/create-hobby", { hobby: newHobby });
console.log(hobbies);
setHobbies((hobbies) => ([
...hobbies,
newHobby,
]));
};
const contextData: HobbyContextData = {
hobbies,
createHobby,
};
return (
<HobbyContext.Provider value={contextData}>
{children}
</HobbyContext.Provider>
);
}
export default HobbyContext;
export { HobbyProvider };
Using HobbyProvider
You can use HobbyProvider to provide access to HobbyContext for every component wrapped inside it.
For example, to use it in every component on /pages/hobbies your implementation would look like:
// /pages/hobbies.tsx
import { useContext, useState } from 'react';
import HobbyContext, { HobbyProvider } from 'path/to/hobbycontext';
export default function HobbiesPage() {
// wrapping the entire page in the `HobbyProvider`
return (
<HobbyProvider initialHobbies={[{ title: 'example hobby' }]}>
<ExampleComponent />
{/* page content */}
</HobbyProvider>
);
}
function ExampleComponent() {
const { hobbies, createHobby } = useContext(HobbyContext);
const [title, setTitle] = useState('');
return (
<div>
hobbies: {JSON.stringify(hobbies)}
<div>
<input
value={title}
onChange={(e) => setTitle(e.target.value)}
/>
<button onClick={() => createHobby(title)}>Create hobby</button>
</div>
</div>
);
}
Similarly, to make the context available throughout your entire website, you can use HobbyProvider in
/pages/_app.tsx.
Using getServerSideProps
To retrieve the initialHobbies from the database, your getServerSideProps would look something like this:
// /pages/hobbies.tsx
import type { Hobby } from '#prisma/client';
export async function getServerSideProps() {
// note: there is no need to use `Hobby[]` as prisma will automatically give you the correct return
// type depending on your query
const initialHobbies: Hobby[] = await prisma.hobby.findMany();
return {
props: {
initialHobbies,
},
};
}
You would have to update your page component to receive the props from getServerSideProps and set initialHobbies on HobbyProvider:
// /pages/hobbies.tsx
import type { InferGetServerSidePropsType } from 'next';
export default function HobbiesPage({ initialHobbies }: InferGetServerSidePropsType<typeof getServerSideProps>) {
return (
<HobbyProvider initialHobbies={initialHobbies}>
<ExampleComponent />
</HobbyProvider>
);
}
Note your page component and getServerSideProps function have to be exported from the same file

AgGrid - How can i have radio button filter instead of checkbox?

I have a custom filter values such as:
filterParams: {
values: ['Admin', 'Proje Yöneticisi', 'Muhasebe'],
defaultToNothingSelected: true,
suppressSelectAll: true
},
However, I can choose multiple values like this. But I don't want to do that, I want to choose only one value instead of multiple choices.
Is there a way to convert this checkbox filter into a radio filter?
Thanks.
You can make a custom filter and there is a video on it: https://www.youtube.com/watch?v=yO3_nTyDv6o
Create a component like this, i am dynamically looking up the options to be displayed based on the extra column parameters supplied in the column def (e.g. thats where props.meta comes in)
import { Button, Radio, RadioGroup, Stack } from "#chakra-ui/react";
import { IFilterParams } from "ag-grid-community";
import React from "react";
import { IRegistryDataColumn } from "../../../../models/RegistryDataColumn";
interface IProps extends IFilterParams {
meta?: IRegistryDataColumn;
}
interface IOption {
value: string;
label: string;
}
export const FilterRadio = React.forwardRef((props: IProps, ref) => {
const [radioOptions, setRadioOptions] = React.useState<IOption[]>([]);
const [filterState, setFilterState] = React.useState<string>();
const handleClear = () => {
setFilterState(undefined);
};
// expose AG Grid Filter Lifecycle callbacks
React.useImperativeHandle(ref, () => {
return {
isFilterActive() {
return filterState !== undefined;
},
doesFilterPass(params) {
const isPass =
params.data[props.colDef.field as string] === filterState;
return isPass;
},
getModel() {},
setModel() {},
};
});
React.useEffect(() => {
props.filterChangedCallback();
}, [filterState]);
React.useEffect(() => {
const radioOptionsUpdate: IOption[] = [];
if (props.meta?.radio_options) {
Object.entries(props.meta.radio_options).forEach(([key, value]) => {
radioOptionsUpdate.push({ value: value.value, label: value.label });
});
}
setRadioOptions(radioOptionsUpdate);
}, [props.meta?.radio_options]);
return (
<Stack p={4} spacing={6} style={{ display: "inline-block" }}>
<Button size="sm" onClick={handleClear}>
Clear filter
</Button>
<RadioGroup onChange={setFilterState} value={filterState}>
<Stack spacing={4}>
{radioOptions.map((option) => (
<Radio key={option.value} value={option.value}>
{option.label}
</Radio>
))}
</Stack>
</RadioGroup>
</Stack>
);
});
And then include it in the column definition:
newCol.filter = FilterRadio;

Cannot access GeoJSON layer in react-leaflet

I am using React Leaflet to render Leaflet map and its GeoJSON component to render polygons. I am trying to implement dragging multiple polygons together at once, as a group.
I added Leaflet.Path.Drag library and tried to reuse this code. I am able to get transformation matrix which is in parent's state. If I want to apply this matrix to multiple polygons with _transform method, it doesn't work. I think the reason is that matrix is not applied to correct layers, but I have no idea how to fix this.
codesandbox.io
App.js
import React from "react";
import { MapContainer, GeoJSON, TileLayer } from "react-leaflet";
import { geoJson, latLngBounds } from "leaflet";
import "./styles.css";
import "leaflet/dist/leaflet.css";
import { GeoJsonContainer } from "./GeoJsonContainer";
const objects = [
{
polygon: {
type: "FeatureCollection",
features: [
{
type: "Feature",
properties: {},
geometry: {
type: "Polygon",
coordinates: [
[
[-104.98569488525392, 39.63431579014969],
[-104.98569488525392, 39.64165260123419],
[-104.97161865234376, 39.64165260123419],
[-104.97161865234376, 39.63431579014969]
]
]
}
}
]
}
},
{
polygon: {
type: "FeatureCollection",
features: [
{
type: "Feature",
properties: {},
geometry: {
type: "Polygon",
coordinates: [
[
[-105.02964019775392, 39.6206315500488],
[-105.02964019775392, 39.65685252543906],
[-104.99067306518556, 39.65685252543906],
[-104.99067306518556, 39.6206315500488]
]
]
}
}
]
}
}
];
const getPolygonPointFromBounds = (latLngBounds) => {
const center = latLngBounds.getCenter();
const latlngs = [];
latlngs.push(latLngBounds.getSouthWest()); //bottom left
latlngs.push({ lat: latLngBounds.getSouth(), lng: center.lng }); //bottom center
latlngs.push(latLngBounds.getSouthEast()); //bottom right
latlngs.push({ lat: center.lat, lng: latLngBounds.getEast() }); // center right
latlngs.push(latLngBounds.getNorthEast()); //top right
latlngs.push({
lat: latLngBounds.getNorth(),
lng: latLngBounds.getCenter().lng
}); //top center
latlngs.push(latLngBounds.getNorthWest()); //top left
latlngs.push({
lat: latLngBounds.getCenter().lat,
lng: latLngBounds.getWest()
}); //center left
return latlngs;
};
export default function App() {
const [matrix, setMatrix] = React.useState(null);
let newBounds = [];
let selectBoundingBox = [];
objects.forEach((building) => {
const polygonBounds = geoJson(building.polygon).getBounds();
newBounds = [...newBounds, polygonBounds];
});
const polygonPoints = getPolygonPointFromBounds(latLngBounds(newBounds));
const convertedData = polygonPoints.map((point) => [point.lng, point.lat]);
convertedData.push([polygonPoints[0].lng, polygonPoints[0].lat]);
selectBoundingBox = convertedData;
let selectBoxData = null;
if (selectBoundingBox) {
selectBoxData = {
type: "FeatureCollection",
features: [
{
type: "Feature",
properties: {},
geometry: {
type: "Polygon",
coordinates: [selectBoundingBox]
}
}
]
};
}
const handleFeature = (layer) => {
layer.makeDraggable();
layer.dragging.enable();
layer.on("drag", function (e) {
setMatrix(layer.dragging._matrix);
});
};
return (
<MapContainer center={[39.63563779557324, -104.99234676361085]} zoom={12}>
<TileLayer
attribution='&copy OpenStreetMap contributors'
url="https://{s}.tile.osm.org/{z}/{x}/{y}.png"
/>
{objects.map((object, i) => (
<GeoJsonContainer data={object} key={i} matrix={matrix} />
))}
<GeoJSON
data={selectBoxData}
style={() => ({
color: "green",
weight: 3,
opacity: 0.5
})}
draggable={true}
onEachFeature={(feature, layer) => handleFeature(layer)}
></GeoJSON>
</MapContainer>
);
}
GeoJsonContainer.js
import React from "react";
import { GeoJSON } from "react-leaflet";
require("leaflet-path-drag");
export const GeoJsonContainer = (props) => {
const geoJSONRef = React.useRef(null);
const layerRef = React.useRef(null);
React.useEffect(() => {
if (geoJSONRef?.current?._layers) {
console.log("mount layers", geoJSONRef.current?._layers);
}
}, []);
React.useEffect(() => {
if (geoJSONRef?.current._layers && props.matrix) {
console.log("transform layers", geoJSONRef.current._layers);
const key = Object.keys(geoJSONRef.current._layers)[0];
const geoJSONElementLayer = geoJSONRef.current._layers[key];
if (geoJSONElementLayer) {
console.log("geoJSONElementLayer", geoJSONElementLayer);
console.log("layerRef.current", layerRef.current);
geoJSONElementLayer._transform(props.matrix);
layerRef.current._transform(props.matrix);
}
}
}, [props.matrix]);
const handleFeature = (layer) => {
console.log("handleFeature layer", layer);
layerRef.current = layer;
layer.makeDraggable();
layer.dragging.enable();
};
return (
<GeoJSON
ref={geoJSONRef}
data={props.data.polygon}
style={() => ({
color: "#3388ff",
weight: 3,
opacity: 1
})}
dragging={true}
onEachFeature={(feature, layer) => handleFeature(layer)}
></GeoJSON>
);
};
Regarding
If I want to apply this matrix to multiple polygons with _transform
method, it doesn't work
This is the expected behavior in React since matrix prop needs to be immutable meaning a new array needs to be passed each time there is a change:
layer.on("drag", function (e) {
setMatrix([...layer.dragging._matrix]);
});
instead of:
layer.on("drag", function (e) {
setMatrix(layer.dragging._matrix);
});
This way GeoJsonContainer component should get re-rendered as expected.
Another matter concerns Leaflet.Path.Drag plugin, according to the referenced thread, in fact both drop and dropend events needs to be captured to properly apply transformation, so maybe instead of matrix prop, introduce a transform prop to keep matrix array and a flag to determine whether drop or is dropend event is triggered:
const handleFeature = (layer) => {
layer.makeDraggable();
layer.dragging.enable();
layer.on("drag", function (e) {
setTransform({matrix: layer.dragging._matrix, "end": false});
});
layer.on("dragend", function (e) {
setTransform({matrix: layer.dragging._matrix, "end": true});
});
};
and pass it into GeoJsonContainer component to apply geometry transform:
React.useEffect(() => {
if (props.transform) {
geoJSONRef.current.eachLayer((layer) => {
if (props.transform.end) dragDropTransform(layer);
else __dragTransform(layer);
});
}
}, [props.transform]);
where
function __dragTransform(layer) {
layer._transform(props.transform.matrix);
}
function dragDropTransform(layer) {
layer.dragging._transformPoints(props.transform.matrix);
layer._updatePath();
layer._project();
layer._transform(null);
}
Updated live demo
A possible improvement(s):
in the provided example two instances of JSON layers are instantiated, maybe to consider create a single instance of GeoJSON and apply a style per geometry?
Solution improvements proposal
In the provided example two layers are instantiated:
in App component
GeoJSON layer which renders a single outer geometry (polygon)
and in GeoJsonContainer component another one
GeoJSON layer which in turn renders a two inner geometries (polygons)
How about to merge both GeoJSON objects, something like this:
const dataSource = {...selectBoxData,...objects[0],...objects[1]}
and create a single layer instead:
<GeoJSON data={dataSource}></GeoJSON>
This way twice initialization for dragable layers could be avoided (refactoring of duplicated code)

How to use a checkbox for a boolean data with ag-grid

I have searched for awhile now and haven't seen any real example of this.
I am using ag-grid-react and I would like for a column that holds a boolean to represent that boolean with a checkbox and update the object in the rowData when changed.
I know there is checkboxSelection and I tried using it like what I have below, but realized while it's a checkbox, it's not linked to the data and is merely for selecting a cell.
var columnDefs = [
{ headerName: 'Refunded', field: 'refunded', checkboxSelection: true,}
]
So is there a way to do what I am looking for with ag-grid and ag-grid-react?
You should use the cellRenderer property
const columnDefs = [{ headerName: 'Refunded',
field: 'refunded',
editable:true,
cellRenderer: params => {
return `<input type='checkbox' ${params.value ? 'checked' : ''} />`;
}
}];
I was stuck in the same problem , this is the best I could come up with but I wasn't able to bind the value to this checkbox.
I set the cell property editable to true , now if you want to change the actual value you have to double click the cell and type true or false.
but this is as far as I went and I decided to help you , I know it doesn't 100% solve it all but at least it solved the data presentation part.
incase you found out how please share your answers with us.
What about this? It's on Angular and not on React, but you could get the point:
{
headerName: 'Enabled',
field: 'enabled',
cellRendererFramework: CheckboxCellComponent
},
And here is the checkboxCellComponent:
#Component({
selector: 'checkbox-cell',
template: `<input type="checkbox" [checked]="params.value" (change)="onChange($event)">`,
styleUrls: ['./checkbox-cell.component.css']
})
export class CheckboxCellComponent implements AfterViewInit, ICellRendererAngularComp {
#ViewChild('.checkbox') checkbox: ElementRef;
public params: ICellRendererParams;
constructor() { }
agInit(params: ICellRendererParams): void {
this.params = params;
}
public onChange(event) {
this.params.data[this.params.colDef.field] = event.currentTarget.checked;
}
}
Let me know
We can use cellRenderer to show checkbox in grid, which will work when you want to edit the field also. Grid will not update the checkbox box value in the gridoption - rowdata directly till you do not update node with respective field in node object which can be access by params object.
params.node.data.fieldName = params.value;
here fieldName is field of the row.
{
headerName: "display name",
field: "fieldName",
cellRenderer: function(params) {
var input = document.createElement('input');
input.type="checkbox";
input.checked=params.value;
input.addEventListener('click', function (event) {
params.value=!params.value;
params.node.data.fieldName = params.value;
});
return input;
}
}
Here's how to create an agGrid cell renderer in Angular to bind one of your columns to a checkbox.
This answer is heavily based on the excellent answer from user2010955's answer above, but with a bit more explanation, and brought up-to-date with the latest versions of agGrid & Angular (I was receiving an error using his code, before adding the following changes).
And yes, I know this question was about the React version of agGrid, but I'm sure I won't be the only Angular developer who stumbles on this StackOverflow webpage out of desperation, trying to find an Angular solution to this problem.
(Btw, I can't believe it's 2020, and agGrid for Angular doesn't come with a checkbox renderer included by default. Seriously ?!!)
First, you need to define a renderer component:
import { Component } from '#angular/core';
import { ICellRendererAngularComp } from 'ag-grid-angular';
import { ICellRendererParams } from 'ag-grid-community';
#Component({
selector: 'checkbox-cell',
template: `<input type="checkbox" [checked]="params.value" (change)="onChange($event)">`
})
export class CheckboxCellRenderer implements ICellRendererAngularComp {
public params: ICellRendererParams;
constructor() { }
agInit(params: ICellRendererParams): void {
this.params = params;
}
public onChange(event) {
this.params.data[this.params.colDef.field] = event.currentTarget.checked;
}
refresh(params: ICellRendererParams): boolean {
return true;
}
}
Next, you need to tell your #NgModule about it:
import { CheckboxCellRenderer } from './cellRenderers/CheckboxCellRenderer';
. . .
#NgModule({
declarations: [
AppComponent,
CheckboxCellRenderer
],
imports: [
BrowserModule,
AgGridModule.withComponents([CheckboxCellRenderer])
],
providers: [],
bootstrap: [AppComponent]
})
In your Component which is displaying the agGrid, you need to import your renderer:
import { CheckboxCellRenderer } from './cellRenderers/CheckboxCellRenderer';
Let's define a new columns for our grid, some of which will use this new renderer:
#Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.scss']
})
export class AppComponent implements OnInit {
#ViewChild('exampleGrid', {static: false}) agGrid: AgGridAngular;
columnDefs = [
{ headerName: 'Last name', field: 'lastName', editable: true },
{ headerName: 'First name', field: 'firstName', editable: true },
{ headerName: 'Subscribed', field: 'subscribed', cellRenderer: 'checkboxCellRenderer' },
{ headerName: 'Is overweight', field: 'overweight', cellRenderer: 'checkboxCellRenderer' }
];
frameworkComponents = {
checkboxCellRenderer: CheckboxCellRenderer
}
}
And now, when you're creating your agGrid, you need to tell it about the home-made framework components which you're using:
<ag-grid-angular #exampleGrid
style="height: 400px;"
class="ag-theme-material"
[rowData]="rowData"
[columnDefs]="columnDefs"
[frameworkComponents]="frameworkComponents" >
</ag-grid-angular>
Phew!
Yeah... it took me a while to work out how to make all the pieces fit together. agGrid's own website really should've included an example like this...
The code below helps address the issue. The downside is that the normal events in gridOptions will not fired (onCellEditingStarted, onCellEditingStopped,onCellValueChanged etc).
var columnDefs = [...
{headerName: "Label", field: "field",editable: true,
cellRenderer: 'booleanCellRenderer',
cellEditor:'booleanEditor'
}
];
//register the components
$scope.gridOptions = {...
components:{
booleanCellRenderer:BooleanCellRenderer,
booleanEditor:BooleanEditor
}
};
function BooleanCellRenderer() {
}
BooleanCellRenderer.prototype.init = function (params) {
this.eGui = document.createElement('span');
if (params.value !== "" || params.value !== undefined || params.value !== null) {
var checkedStatus = params.value ? "checked":"";
var input = document.createElement('input');
input.type="checkbox";
input.checked=params.value;
input.addEventListener('click', function (event) {
params.value=!params.value;
//checked input value has changed, perform your update here
console.log("addEventListener params.value: "+ params.value);
});
this.eGui.innerHTML = '';
this.eGui.appendChild( input );
}
};
BooleanCellRenderer.prototype.getGui = function () {
return this.eGui;
};
function BooleanEditor() {
}
BooleanEditor.prototype.init = function (params) {
this.container = document.createElement('div');
this.value=params.value;
params.stopEditing();
};
BooleanEditor.prototype.getGui = function () {
return this.container;
};
BooleanEditor.prototype.afterGuiAttached = function () {
};
BooleanEditor.prototype.getValue = function () {
return this.value;
};
BooleanEditor.prototype.destroy = function () {
};
BooleanEditor.prototype.isPopup = function () {
return true;
};
React specific solution
When using React (16.x) functional component with React Hooks make it easy to write your cellRenderer. Here is the function equivalent of what pnunezcalzado had posted earlier.
React component for the cell Renderer
function AgGridCheckbox (props) {
const boolValue = props.value && props.value.toString() === 'true';
const [isChecked, setIsChecked] = useState(boolValue);
const onChanged = () => {
props.setValue(!isChecked);
setIsChecked(!isChecked);
};
return (
<div>
<input type="checkbox" checked={isChecked} onChange={onChanged} />
</div>
);
}
Using it in your grid
Adjust your column def (ColDef) to use this cell renderer.
{
headerName: 'My Boolean Field',
field: 'BOOLFIELD',
cellRendererFramework: AgGridCheckbox,
editable: true,
},
Frameworks - React/Angular/Vue.js
You can easily integrate cell renderers with any JavaScript framework you're using to render ag-Grid, by creating your cell renderers as native framework components.
See this implemented in React in the code segment below:
export default class extends Component {
constructor(props) {
super(props);
this.checkedHandler = this.checkedHandler.bind(this);
}
checkedHandler() {
let checked = event.target.checked;
let colId = this.props.column.colId;
this.props.node.setDataValue(colId, checked);
}
render() {
return (
<input
type="checkbox"
onClick={this.checkedHandler}
checked={this.props.value}
/>
)
}
}
Note: There are no required lifecycle methods when creating cell renderers as framework components.
After creating the renderer, we register it to ag-Grid in gridOptions.frameworkComponents and define it on the desired columns:
// ./index.jsx
this.frameworkComponents = {
checkboxRenderer: CheckboxCellRenderer,
};
this.state = {
columnDefs: [
// ...
{
headerName: 'Registered - Checkbox',
field: 'registered',
cellRenderer: 'checkboxRenderer',
},
// ...
]
// ....
Please see below live samples implemented in the most popular JavaScript frameworks (React, Angular, Vue.js):
React demo.
Angular demo.
Note: When using Angular it is also necessary to pass custom renderers to the #NgModule decorator to allow for dependency injection.
Vue.js demo.
Vanilla JavaScript
You can also implement the checkbox renderer using JavaScript.
In this case, the checkbox renderer is constructed using a JavaScript Class. An input element is created in the ag-Grid init lifecycle method (required) and it's checked attribute is set to the underlying boolean value of the cell it will be rendered in. A click event listener is added to the checkbox which updates this underlying cell value whenever the input is checked/unchecked.
The created DOM element is returned in the getGui (required) lifecycle hook. We have also done some cleanup in the destroy optional lifecycle hook, where we remove the click listener.
function CheckboxRenderer() {}
CheckboxRenderer.prototype.init = function(params) {
this.params = params;
this.eGui = document.createElement('input');
this.eGui.type = 'checkbox';
this.eGui.checked = params.value;
this.checkedHandler = this.checkedHandler.bind(this);
this.eGui.addEventListener('click', this.checkedHandler);
}
CheckboxRenderer.prototype.checkedHandler = function(e) {
let checked = e.target.checked;
let colId = this.params.column.colId;
this.params.node.setDataValue(colId, checked);
}
CheckboxRenderer.prototype.getGui = function(params) {
return this.eGui;
}
CheckboxRenderer.prototype.destroy = function(params) {
this.eGui.removeEventListener('click', this.checkedHandler);
}
After creating our renderer we simply register it to ag-Grid in our gridOptions.components object:
gridOptions.components = {
checkboxRenderer: CheckboxRenderer
}
And define the renderer on the desired column:
gridOptions.columnDefs = [
// ...
{
headerName: 'Registered - Checkbox',
field: 'registered',
cellRenderer: 'checkboxRenderer',
},
// ...
Please see this implemented in the demo below:
Vanilla JavaScript.
Read the full blog post on our website or check out our documentation for a great variety of scenarios you can implement with ag-Grid.
Ahmed Gadir | Developer # ag-Grid
Here is a react hooks version, set columnDef.cellEditorFramework to this component.
import React, {useEffect, forwardRef, useImperativeHandle, useRef, useState} from "react";
export default forwardRef((props, ref) => {
const [value, setValue] = useState();
if (value !== ! props.value) {
setValue(!props.value);
}
const inputRef = useRef();
useImperativeHandle(ref, () => {
return {
getValue: () => {
return value;
}
};
});
const onChange= e => {
setValue(!value);
}
return (<div style={{paddingLeft: "15px"}}><input type="checkbox" ref={inputRef} defaultChecked={value} onChange={onChange} /></div>);
})
I also have the following cell renderer which is nice
cellRenderer: params => {
return `<i class="fa fa-${params.value?"check-":""}square-o" aria-hidden="true"></i>`;
},
In the columnDefs, add a checkbox column. Don't need set the cell property editable to true
columnDefs: [
{ headerName: '', field: 'checkbox', cellRendererFramework: CheckboxRenderer, width:30},
...]
The CheckboxRenderer
export class CheckboxRenderer extends React.Component{
constructor(props) {
super(props);
this.state={
value:props.value
};
this.handleCheckboxChange=this.handleCheckboxChange.bind(this);
}
handleCheckboxChange(event) {
this.props.data.checkbox=!this.props.data.checkbox;
this.setState({value: this.props.data.checkbox});
}
render() {
return (
<Checkbox
checked={this.state.value}
onChange={this.handleCheckboxChange}></Checkbox>);
}
}
The array of the string values doesn't work for me, but array of [true,false] is working.
{
headerName: 'Refunded',
field: 'refunded',
cellEditor: 'popupSelect',
cellEditorParams: {
cellRenderer: RefundedCellRenderer,
values: [true, false]
}
},
function RefundedCellRenderer(params) {
return params.value;
}
You can use boolean (true or false)
I try this and it's work :
var columnDefs = [
{
headerName: 'Refunded',
field: 'refunded',
editable: true,
cellEditor: 'booleanEditor',
cellRenderer: booleanCellRenderer
},
];
Function checkbox editor
function getBooleanEditor() {
// function to act as a class
function BooleanEditor() {}
// gets called once before the renderer is used
BooleanEditor.prototype.init = function(params) {
// create the cell
var value = params.value;
this.eInput = document.createElement('input');
this.eInput.type = 'checkbox';
this.eInput.checked = value;
this.eInput.value = value;
};
// gets called once when grid ready to insert the element
BooleanEditor.prototype.getGui = function() {
return this.eInput;
};
// focus and select can be done after the gui is attached
BooleanEditor.prototype.afterGuiAttached = function() {
this.eInput.focus();
this.eInput.select();
};
// returns the new value after editing
BooleanEditor.prototype.getValue = function() {
return this.eInput.checked;
};
// any cleanup we need to be done here
BooleanEditor.prototype.destroy = function() {
// but this example is simple, no cleanup, we could
// even leave this method out as it's optional
};
// if true, then this editor will appear in a popup
BooleanEditor.prototype.isPopup = function() {
// and we could leave this method out also, false is the default
return false;
};
return BooleanEditor;
}
And then booleanCellRenderer function
function booleanCellRenderer(params) {
var value = params.value ? 'checked' : 'unchecked';
return '<input disabled type="checkbox" '+ value +'/>';
}
Let the grid know which columns and what data to use
var gridOptions = {
columnDefs: columnDefs,
pagination: true,
defaultColDef: {
filter: true,
resizable: true,
},
onGridReady: function(params) {
params.api.sizeColumnsToFit();
},
onCellValueChanged: function(event) {
if (event.newValue != event.oldValue) {
// do stuff
// such hit your API update
event.data.refunded = event.newValue; // Update value of field refunded
}
},
components:{
booleanCellRenderer: booleanCellRenderer,
booleanEditor: getBooleanEditor()
}
};
Setup the grid after the page has finished loading
document.addEventListener('DOMContentLoaded', function() {
var gridDiv = document.querySelector('#myGrid');
// create the grid passing in the div to use together with the columns & data we want to use
new agGrid.Grid(gridDiv, gridOptions);
fetch('$urlGetData').then(function(response) {
return response.json();
}).then(function(data) {
gridOptions.api.setRowData(data);
})
});
Even though it's an old question, I developed a solution that may be interesting.
You can create a cell renderer component for the checkbox then apply it to the columns that must render a checkbox based on a boolean value.
Check the example below:
/*
CheckboxCellRenderer.js
Author: Bruno Carvalho da Costa (brunoccst)
*/
/*
* Function to work as a constructor.
*/
function CheckboxCellRenderer() {}
/**
* Initializes the cell renderer.
* #param {any} params Parameters from AG Grid.
*/
CheckboxCellRenderer.prototype.init = function(params) {
// Create the cell.
this.eGui = document.createElement('span');
this.eGui.classList.add("ag-icon");
var node = params.node;
var colId = params.column.colId;
// Set the "editable" property to false so it won't open the default cell editor from AG Grid.
if (params.colDef.editableAux == undefined)
params.colDef.editableAux = params.colDef.editable;
params.colDef.editable = false;
// Configure it accordingly if it is editable.
if (params.colDef.editableAux) {
// Set the type of cursor.
this.eGui.style["cursor"] = "pointer";
// Add the event listener to the checkbox.
function toggle() {
var currentValue = node.data[colId];
node.setDataValue(colId, !currentValue);
// TODO: Delete this log.
console.log(node.data);
}
this.eGui.addEventListener("click", toggle);
}
// Set if the checkbox is checked.
this.refresh(params);
};
/**
* Returns the GUI.
*/
CheckboxCellRenderer.prototype.getGui = function() {
return this.eGui;
};
/**
* Refreshes the element according to the current data.
* #param {any} params Parameters from AG Grid.
*/
CheckboxCellRenderer.prototype.refresh = function(params) {
var checkedClass = "ag-icon-checkbox-checked";
var uncheckedClass = "ag-icon-checkbox-unchecked";
// Add or remove the classes according to the value.
if (params.value) {
this.eGui.classList.remove(uncheckedClass);
this.eGui.classList.add(checkedClass);
} else {
this.eGui.classList.remove(checkedClass);
this.eGui.classList.add(uncheckedClass);
}
// Return true to tell the grid we refreshed successfully
return true;
}
/*
The code below does not belong to the CheckboxCellRenderer.js anymore.
It is the main JS that creates the AG Grid instance and structure.
*/
// specify the columns
var columnDefs = [{
headerName: "Make",
field: "make"
}, {
headerName: "Model",
field: "model"
}, {
headerName: "Price",
field: "price"
}, {
headerName: "In wishlist (editable)",
field: "InWishlist",
cellRenderer: CheckboxCellRenderer
}, {
headerName: "In wishlist (not editable)",
field: "InWishlist",
cellRenderer: CheckboxCellRenderer,
editable: false
}];
// specify the data
var rowData = [{
make: "Toyota",
model: "Celica",
price: 35000,
InWishlist: true
}, {
make: "Toyota 2",
model: "Celica 2",
price: 36000,
InWishlist: false
}];
// let the grid know which columns and what data to use
var gridOptions = {
columnDefs: columnDefs,
defaultColDef: {
editable: true
},
rowData: rowData,
onGridReady: function() {
gridOptions.api.sizeColumnsToFit();
}
};
// wait for the document to be loaded, otherwise
// ag-Grid will not find the div in the document.
document.addEventListener("DOMContentLoaded", function() {
// lookup the container we want the Grid to use
var eGridDiv = document.querySelector('#myGrid');
// create the grid passing in the div to use together with the columns & data we want to use
new agGrid.Grid(eGridDiv, gridOptions);
});
<!DOCTYPE html>
<html>
<head>
<script src="https://unpkg.com/ag-grid/dist/ag-grid.js"></script>
</head>
<body>
<div id="myGrid" style="height: 115px;" class="ag-fresh"></div>
</body>
</html>
Please note that I needed to disable the editable property before ending the init function or else AG Grid would render the default cell editor for the field, having a weird behavior.
import React, { Component } from 'react';
export class CheckboxRenderer extends Component {
constructor(props) {
super(props);
if (this.props.colDef.field === 'noRestrictions') {
this.state = {
value: true,
disable: false
};
}
else if (this.props.colDef.field === 'doNotBuy') {
this.state = {
value: false,
disable: true
};
}
this.handleCheckboxChange = this.handleCheckboxChange.bind(this);
}
handleCheckboxChange(event) {
//this.props.data.checkbox=!this.props.data.checkbox; ={this.state.show}
//this.setState({value: this.props.data.checkbox});
if (this.state.value) { this.setState({ value: false }); }
else { this.setState({ value: true }); }
alert(this.state.value);
//check for the last row and check for the columnname and enable the other columns
}
render() {
return (
<input type= 'checkbox' checked = { this.state.value } disabled = { this.state.disable } onChange = { this.handleCheckboxChange } />
);
}
}
export default CheckboxRenderer;
import React, { Component } from 'react';
import './App.css';
import { AgGridReact } from 'ag-grid-react';
import CheckboxRenderer from './CheckboxRenderer';
import 'ag-grid/dist/styles/ag-grid.css';
import 'ag-grid/dist/styles/ag-theme-balham.css';
class App extends Component {
constructor(props) {
super(props);
let enableOtherFields = false;
this.state = {
columnDefs: [
{ headerName: 'Make', field: 'make' },
{
headerName: 'noRestrictions', field: 'noRestrictions',
cellRendererFramework: CheckboxRenderer,
cellRendererParams: { checkedVal: true, disable: false },
onCellClicked: function (event) {
// event.node.columnApi.columnController.gridColumns[1].colDef.cellRendererParams.checkedVal=!event.node.columnApi.columnController.gridColumns[1].colDef.cellRendererParams.checkedVal;
// event.node.data.checkbox=!event.data.checkbox;
let currentNode = event.node.id;
event.api.forEachNode((node) => {
if (node.id === currentNode) {
node.data.checkbox = !node.data.checkbox;
}
//if(!node.columnApi.columnController.gridColumns[1].colDef.cellRendererParams.checkedVal){ // checkbox is unchecked
if (node.data.checkbox === false && node.data.checkbox !== 'undefined') {
enableOtherFields = true;
} else {
enableOtherFields = false;
}
//alert(node.id);
//alert(event.colDef.cellRendererParams.checkedVal);
}); alert("enable other fields:" + enableOtherFields);
}
},
{
headerName: 'doNotBuy', field: 'doNotBuy',
cellRendererFramework: CheckboxRenderer,
cellRendererParams: { checkedVal: false, disable: true }
},
{ headerName: 'Price', field: 'price', editable: true }
],
rowData: [
{ make: "Toyota", noRestrictions: true, doNotBuy: false, price: 35000 },
{ make: "Ford", noRestrictions: true, doNotBuy: false, price: 32000 },
{ make: "Porsche", noRestrictions: true, doNotBuy: false, price: 72000 }
]
};
}
componentDidMount() {
}
render() {
return (
<div className= "ag-theme-balham" style = {{ height: '200px', width: '800px' }}>
<AgGridReact enableSorting={ true }
enableFilter = { true}
//pagination={true}
columnDefs = { this.state.columnDefs }
rowData = { this.state.rowData } >
</AgGridReact>
</div>
);
}
}
export default App;
Boolean data in present part:
function booleanCellRenderer(params) {
var valueCleaned = params.value;
if (valueCleaned === 'T') {
return '<input type="checkbox" checked/>';
} else if (valueCleaned === 'F') {
return '<input type="checkbox" unchecked/>';
} else if (params.value !== null && params.value !== undefined) {
return params.value.toString();
} else {
return null;
}
}
var gridOptions = {
...
components: {
booleanCellRenderer: booleanCellRenderer,
}
};
gridOptions.api.setColumnDefs(
colDefs.concat(JSON.parse('[{"headerName":"Name","field":"Field",
"cellRenderer": "booleanCellRenderer"}]')));
Here's a solution that worked for me. It's mandatory to respect arrow functions to solve context issues.
Component:
import React from "react";
class AgGridCheckbox extends React.Component {
state = {isChecked: false};
componentDidMount() {
let boolValue = this.props.value.toString() === "true";
this.setState({isChecked: boolValue});
}
onChanged = () => {
const checked = !this.state.isChecked;
this.setState({isChecked: checked});
this.props.setValue(checked);
};
render() {
return (
<div>
<input type={"checkbox"} checked={this.state.isChecked} onChange={this.onChanged}/>
</div>
);
}
}
export default AgGridCheckbox;
Column definition object inside columnDefs array:
{
headerName: "yourHeaderName",
field: "yourPropertyNameInsideDataObject",
cellRendererFramework: AgGridCheckbox
}
JSX calling ag-grid:
<div
className="ag-theme-balham"
>
<AgGridReact
defaultColDef={defaultColDefs}
columnDefs={columnDefs}
rowData={data}
/>
</div>
I found a good online example for this feature:
https://stackblitz.com/edit/ag-grid-checkbox?embed=1&file=app/ag-grid-checkbox/ag-grid-checkbox.component.html
The background knowledge is based on the cellRendererFramework : https://www.ag-grid.com/javascript-grid-components/
gridOptions = {
onSelectionChanged: (event: any) => {
let rowData = [];
event.api.getSelectedNodes().forEach(node => {
rowDate = [...rowData, node.data];
});
console.log(rowData);
}
}
You can keep a checkbox on display and edit as following:
headerName: 'header name',
field: 'field',
filter: 'agTextColumnFilter',
cellRenderer: params => this.checkBoxCellEditRenderer(params),
And then create an renderer:
checkBoxCellEditRenderer(params) {
const input = document.createElement('input');
input.type = 'checkbox';
input.checked = params.value;
input.addEventListener('click', () => {
params.value = !params.value;
params.node.data[params.coldDef.field] = params.value;
// you can add here code
});
return input;
}
This is an old question but there is a new answer available if you are using AdapTable in conjunction with AG Grid.
Simply define the column as a Checkbox Column and AdapTable will do it all for you - create the checkbox, check it if the cell value is true, and fire an event each time it is checked:
See: https://demo.adaptabletools.com/formatcolumn/aggridcheckboxcolumndemo
So in the end I somewhat got what I wanted, but in a slightly different way, I used popupSelect and cellEditorParams with values: ['true', 'false']. Of course I don't have an actual check box like I wanted, but it behaves good enough for what I need
{
headerName: 'Refunded',
field: 'refunded',
cellEditor: 'popupSelect',
cellEditorParams: {
cellRenderer: RefundedCellRenderer,
values: ['true', 'false']
}
},
function RefundedCellRenderer(params) {
return params.value;
}

React/Meteor Component not passing Props properly

new to Meteor and running into this issue. I am using Meteor 1.3.3
When I try to pass props from my parent Container to my React Component it keeps throwing an error I will post below.
Here is my React component Prospect.jsx:
import React from 'react'
import { createContainer } from 'meteor/react-meteor-data'
import { Residents } from '/collections/residents.jsx'
import ReactDOM from 'react-dom';
import RaisedButton from 'material-ui/RaisedButton';
// import '/collections/residents.jsx'
class Prospect extends React.Component {
render() {
return(
<div>
<h1>Prospect Resident - {this.props.prospect.name.first} </h1>
<RaisedButton label="Default" />
</div>
)
}
}
Prospect.propTypes = {
// prospect: React.PropTypes.object
}
export default createContainer((params) => {
const paramsId = params.params.prospectId
Meteor.subscribe('residents');
// Meteor.subscribe('resident');
prospect = Residents.find({_id: paramsId}).fetch()
console.log(prospect[0])
return {
prospect: prospect
}
}, Prospect)
and here is my Mongo collection
residents.jsx
import { Mongo } from 'meteor/mongo'
export const Residents = new Mongo.Collection('residents')
const nameSchema = new SimpleSchema({
first: {type: String},
last: {type: String}
})
const residentSchema = new SimpleSchema({
cId: { type: String },
name: { type: nameSchema },
status: { type: String },
})
Residents.attachSchema(residentSchema)
// METHODS
Meteor.methods({
'residents.insert'(resident) {
Residents.insert(resident)
}
})
// PUBLICATIONS
if(Meteor.isServer) {
Meteor.publish('residents', function() {
return Residents.find()
})
Meteor.publish('resident', function(id) {
return Residents.find({_id: id})
})
}
and here is my Route
FlowRouter.route('/prospects/:prospectId}', {
name: 'prospectShow',
action(params) {
mount(LoggedIn, { content:
<MuiThemeProvider muiTheme={getMuiTheme()}>
<Prospect params={{prospectId: params.prospectId}} />
</MuiThemeProvider>
})
}
So when I go to localhost:3000 route I get the error
Prospect.jsx:14Uncaught TypeError: Cannot read property 'name' of undefined
Exception from Tracker recompute function:
debug.js:41 TypeError: Cannot read property '_currentElement' of null
at ReactCompositeComponentWrapper._updateRenderedComponent (ReactCompositeComponent.js:772)
at ReactCompositeComponentWrapper._performComponentUpdate (ReactCompositeComponent.js:753)
at ReactCompositeComponentWrapper.updateComponent (ReactCompositeComponent.js:672)
at ReactCompositeComponentWrapper.receiveComponent (ReactCompositeComponent.js:571)
at Object.receiveComponent (ReactReconciler.js:127)
at ReactCompositeComponentWrapper._updateRenderedComponent (ReactCompositeComponent.js:775)
at ReactCompositeComponentWrapper._performComponentUpdate (ReactCompositeComponent.js:753)
at ReactCompositeComponentWrapper.updateComponent (ReactCompositeComponent.js:672)
at ReactCompositeComponentWrapper.performUpdateIfNecessary (ReactCompositeComponent.js:585)
at Object.performUpdateIfNecessary (ReactReconciler.js:160)
My console.log(prospect[0]) in the container returns the object just fine, and it also works if I pass it in like this
return {
prospect: {name: {first: 'Joe', last: 'Smith'}}
}
So it's something about the returned object I think. Any help would be greatly appreciated, thanks
I ended up going with a solution like this. If anyone wants to answer and explain why this is needed (I thought in meteor 1.3 this wasn't needed anymore) I will accept your answer.
import React from 'react'
import { createContainer } from 'meteor/react-meteor-data'
import { Residents } from '/collections/residents.jsx'
class Prospect extends React.Component {
render() {
if(!this.props.ready){return <span>Loading...</span>}
const { prospect } = this.props
return(
<div>
<h1>{prospect.name.first} {prospect.name.last}</h1>
<div>Company: <b>{prospect.cId}</b></div>
</div>
)
}
}
Prospect.propTypes = {
ready: React.PropTypes.bool.isRequired,
prospect: React.PropTypes.object.isRequired
}
export default createContainer((params) => {
return {
ready: Meteor.subscribe('resident', params.id).ready(),
prospect: Residents.findOne(params.id)
}
}, Prospect)