I am using community version of ag-grid in my project. I am trying add menu button in one of the cell of every row. on clicking of the menu button, there should be menu pop up, which will have Edit/delete/rename options and I need to fire event with row value when any item on menu is clicked.
I am trying to create a cell renderer which will display the button. menu will be hidden initially and on clicking of button, I am changing display using css class. I am seeing the css class is getting added correctly but the menu is still not visible. I checked in the console and it is hidden behind the table. I used position absolute and z-index at various place but ended up with no luck.
I can not use context menu or enterprise menu out of box as I am using community version. can you please help me here? also, is there any better way to achieve this result then let me know. Thanks a lot in advance.
var students = [
{value: 14, type: 'age'},
{value: 'female', type: 'gender'},
{value: "Happy", type: 'mood'},
{value: 21, type: 'age'},
{value: 'male', type: 'gender'},
{value: "Sad", type: 'mood'}
];
var columnDefs = [
{
headerName: "Value",
field: "value",
width: 100
},
{headerName: "Type", field: "type", width: 100},
{headerName: "Action", width: 100, cellRenderer: 'actionMenuRenderer' }
];
var gridOptions = {
columnDefs: columnDefs,
rowData: students,
onGridReady: function (params) {
params.api.sizeColumnsToFit();
},
components:{
actionMenuRenderer: ActionMenuCellRenderer
}
};
function ActionMenuCellRenderer() {
}
ActionMenuCellRenderer.prototype.init = function (params) {
this.eGui = document.createElement('div')
if (params.value !== "" || params.value !== undefined || params.value !== null) {
this.eGui.classList.add('menu');
this.eGui.innerHTML = this.getMenuMarkup();
this.actionBtn = this.eGui.querySelector(`.actionButton`);
this.menuWrapper = this.eGui.querySelector(`.menuWrapper`);
this.actionBtn.addEventListener('click', event => this.onActionBtnClick(event));
}
};
ActionMenuCellRenderer.prototype.getGui = function () {
return this.eGui;
};
ActionMenuCellRenderer.prototype.onActionBtnClick = function() {
alert('hey');
this.menuWrapper.classList.toggle('showMenu');
}
ActionMenuCellRenderer.prototype.getMenuMarkup = function () {
return `
<button type="button" class="actionButton">
menu
</button>
<div class="menuWrapper">
<a class="menuItem">
Edit
</a>
<a class="menuItem">
Delete
</a>
<a class="menuItem">
Duplicate
</a>
</div>
`;
}
My plnkr sample-
plnkr sample
The issue is due to the context menu also renders inside the ag-grid cell. So it does not matter how much z-index you give it can not display it outside the cell renderer div of the ag grid. The solution is we can use the library like Tippys which will render the menu outside the ag-grid main div which will fix the issue. Below is the sample code for react to show the menu on click of a button in ag-grid cell renderer.
There was nice blog by the ag-grid on the same. Here is the reference link
import React, { useState, useEffect, useMemo, useRef } from "react";
import { AgGridReact } from "ag-grid-react";
import Tippy from "#tippyjs/react";
import "ag-grid-community/dist/styles/ag-grid.css";
import "ag-grid-community/dist/styles/ag-theme-alpine.css";
function ActionsMenu(props) {
const tippyRef = useRef();
const [visible, setVisible] = useState(false);
const show = () => setVisible(true);
const hide = () => setVisible(false);
const menu = (
<div className="menu-container">
<div className="menu-item" onClick={hide}>
Create
</div>
<div className="menu-item" onClick={hide}>
Edit
</div>
<div className="menu-item" onClick={hide}>
Delete
</div>
</div>
);
return (
<Tippy
ref={tippyRef}
content={menu}
visible={visible}
onClickOutside={hide}
allowHTML={true}
arrow={false}
appendTo={document.body}
interactive={true}
placement="right"
// moveTransition='transform 0.1s ease-out'
>
<button onClick={visible ? hide : show}>Actions</button>
</Tippy>
);
}
const frameworkComponents = {
ActionsMenu: ActionsMenu,
};
export default function App() {
const [rowData, setRowData] = useState([
{ make: "Ford", model: "Focus", price: 20000 },
{ make: "Toyota", model: "Celica", price: 40000 },
{ make: "BMW", model: "4 Series", price: 50000 },
]);
const [columnDefs, setColumnDefs] = useState([
{ field: "make" },
{ field: "model" },
{ field: "price" },
{ field: "", cellRenderer: "ActionsMenu" },
]);
const defaultColDef = useMemo(
() => ({
sortable: true,
filter: true,
}),
[]
);
useEffect(() => {
fetch("https://www.ag-grid.com/example-assets/row-data.json")
.then((result) => result.json())
.then((r) => setRowData(r));
}, []);
return (
<div className="ag-theme-alpine" style={{ height: 500, width: "100%" }}>
<AgGridReact
rowData={rowData}
columnDefs={columnDefs}
defaultColDef={defaultColDef}
frameworkComponents={frameworkComponents}
/>
</div>
);
}
Related
After implementing the drag and drop feature on AG Grid table, I'm looking for a way to get the current state with the updated order/index of rows. My goal is to persist the table data after changing the order, but can't find the respective state of the current order.
I'd appreciate any help or any idea.
Sandbox demo and example code below
import React from "react";
import { AgGridReact } from "ag-grid-react";
import "ag-grid-community/dist/styles/ag-grid.css";
import "ag-grid-community/dist/styles/ag-theme-alpine.css";
function App() {
const [gridApi, setGridApi] = React.useState(null);
const [gridColumnApi, setGridColumnApi] = React.useState(null);
const onGridReady = (params) => {
setGridApi(params.api);
setGridColumnApi(params.columnApi);
};
const defaultColDef = {
flex: 1,
editable: true
};
const columnDefs = [
{
headerName: "Name",
field: "name",
rowDrag: true
},
{ headerName: "stop", field: "stop" },
{
headerName: "duration",
field: "duration"
}
];
const rowData = React.useMemo(
() => [
{
name: "John",
stop: 10,
duration: 5
},
{
name: "David",
stop: 15,
duration: 8
},
{
name: "Dan",
stop: 20,
duration: 6
}
],
[]
);
return (
<div>
<h1 align="center">React-App</h1>
<div>
<div className="ag-theme-alpine" style={{ height: "700px" }}>
<AgGridReact
columnDefs={columnDefs}
rowData={rowData}
defaultColDef={defaultColDef}
onGridReady={onGridReady}
rowDragManaged={true}
></AgGridReact>
</div>
</div>
</div>
);
}
export default App;
You can get the order of the rows inside the grid by iterating over them using the Grid API method forEachNode:
API for Row Nodes
const rows = [];
gridApi.forEachNodeAfterFilterAndSort((node) => rows.push(node.data));
console.log(rows);
See this implemented in the following sample.
You're currently using managed dragging by passing rowManagedDragging={true}, which means the AgGridReact component is managing the row order state.
If you want to maintain row order state outside the component, you need to use Unmanaged Dragging.
Add a handler for onRowDragMove, and use the node and overIndex or overNode properties of the event to update your local event order state, and pass it to the AgGridReact component to re-render.
Take a look at this example from the docs
I am new to Vuejs. I am using Primevue library to build the api using the composition vuejs 3.
my problem is that menu is not updating. I want to hide the show button when the element is shown and vice versa. I search all the internet and tried all the solutions I found but in vain.
Any help is appreciated, thank you
export default {
name: "Quote",
components: {
loader: Loader,
"p-breadcrumb": primevue.breadcrumb,
"p-menu": primevue.menu,
"p-button": primevue.button,
},
setup() {
const {
onMounted,
ref,
watch,
} = Vue;
const data = ref(frontEndData);
const quoteIsEdit = ref(false);
const toggle = (event) => {
menu.value.toggle(event);
};
const quote = ref({
display_item_date: true,
display_tax: true,
display_discount: false,
});
const menu = ref();
const items = ref([
{
label: data.value.common_lang.options,
items: [{
visible: quote.value.display_item_date,
label: data.value.common_lang.hide_item_date,
icon: 'pi pi-eye-slash',
command: (event) => {
quote.value.display_item_date = !quote.value.display_item_date;
}
},
{
visible: !quote.value.display_item_date,
label: data.value.common_lang.unhide_item_date,
icon: 'pi pi-eye',
command: () => {
quote.value.display_item_date = !quote.value.display_item_date;
}
}
]
]);
}
return {
data,
quoteIsEdit,
menu,
items,
toggle
};
},
template:
`
<div class="container-fluid" v-cloak>
<div class="text-right">
<p-menu id="overlay_menu" ref="menu" :model="items" :popup="true"></p-menu>
<p-button icon="pi pi-cog" class="p-button-rounded p-button-primary m-2" #click="toggle" aria-haspopup="true" aria-controls="overlay_menu"></p-button>
<p-button :label="data.common_lang.save + ' ' + data.common_lang.quote" class=" m-2" /></p-button>
</div>
</div>
`
};
The problem is the items subproperty change is not reactive, so the items.value.items[].visible props are not automatically updated when quote.value.display_item_date changes.
One solution is to make items a computed prop, so that it gets re-evaluated upon changes to the inner refs:
// const items = ref([...])
const items = computed(() => [...])
demo
This is the code I am trying to rebuild using functional component, but my arrays do not behave correctly.
EXPECTED RESULT: https://stackblitz.com/edit/antd-showhidecolumns
My forked functional component version:
MY WORK https://stackblitz.com/edit/antd-showhidecolumns-rdyc8h
Main issue here is I am not able to show/hide column cells, I am not sure why my array is different when I use the same method as the original code.
My code:
const onChange = (e) => {
let { checkedColumns } = colmenu;
if (e.target.checked) {
checkedColumns = checkedColumns.filter((id) => {
return id !== e.target.id;
});
console.log('if checked columns is', checkedColumns);
} else if (!e.target.checked) {
checkedColumns.push(e.target.id);
console.log('elseif checked columns', checkedColumns);
}
const filtered = checkedColumns.filter((el) => {
return el.dataIndex !== checkedColumns.el;
});
console.log('filtered items', filtered);
setColmenu({ ...colmenu, columns: filtered });
};
working version from the old code (class component)
onChange = (e) => {
var checkedColumns = this.state.checkedColumns
if(e.target.checked){
checkedColumns = checkedColumns.filter(id => {return id !== e.target.id})
}
else if(!e.target.checked){
checkedColumns.push(e.target.id)
}
var filtered = this.state.initialColumns;
for(var i =0;i< checkedColumns.length; i++)
filtered = filtered.filter(el => {return el.dataIndex !== checkedColumns[i]})
this.setState({columns: filtered, checkedColumns: checkedColumns})
}
Something really went wrong with your code (or homework i guess?)
Please have a look at least at the docs for React.useState to set some basics.
First you should init your initalColumns and later you should filter on them.
Additional i init the checkColumns with the correct values and changed the wrong logic for changing them.
Have a look how the filtering is done via Array.includes maybe someone will ask for this ;-)
Another point is that you may split the state object in separate primitive states.
Nevertheless here is a working stackblitz and the depending code.
import React from 'react';
import ReactDOM from 'react-dom';
import 'antd/dist/antd.css';
import './index.css';
import { Table, Button, Dropdown, Menu, Checkbox } from 'antd';
const App = () => {
const columns = [
{
title: 'Description',
dataIndex: 'description',
},
{
title: 'Employees',
dataIndex: 'employees',
},
];
const [colmenu, setColmenu] = React.useState({
value: false,
checkedColumns: ['description', 'employees'],
visibleMenuSettings: false,
columns,
initialColumns: columns,
});
const onChange = (e) => {
let { checkedColumns, columns, initialColumns } = colmenu;
if (!e.target.checked) {
checkedColumns = checkedColumns.filter((id) => {
return id !== e.target.id;
});
console.log('if checked columns is', checkedColumns);
} else if (e.target.checked) {
checkedColumns.push(e.target.id);
console.log('elseif checked columns', checkedColumns);
}
console.log(columns);
columns = initialColumns.filter((col) =>
checkedColumns.includes(col.dataIndex)
);
setColmenu({ ...colmenu, columns, checkedColumns });
};
const handleVisibleChange = (flag) => {
setColmenu({ ...colmenu, visibleMenuSettings: flag });
};
const menu = (
<Menu>
<Menu.ItemGroup title="Columns">
<Menu.Item key="0">
<Checkbox id="description" onChange={onChange} defaultChecked>
Description
</Checkbox>
</Menu.Item>
<Menu.Item key="1">
<Checkbox id="employees" onChange={onChange} defaultChecked>
Employees
</Checkbox>
</Menu.Item>
</Menu.ItemGroup>
</Menu>
);
const dataSource = [
{
key: '1',
description: 'Holiday 1',
employees: '79',
},
{
key: '2',
description: 'Holiday 2',
employees: '12',
},
{
key: '3',
description: 'Holiday 3',
employees: '0',
},
];
return (
<div>
<div className="row">
<div className="col-12 mb-3 d-flex justify-content-end align-items-center">
<Dropdown
overlay={menu}
onVisibleChange={handleVisibleChange}
visible={colmenu.visibleMenuSettings}
>
<Button>Show/Hide Columns</Button>
</Dropdown>
</div>
</div>
<div className="row">
<div className="col-12">
<Table
columns={colmenu.columns}
dataSource={dataSource}
size="small"
pagination={{
pageSizeOptions: ['20', '50'],
showSizeChanger: true,
}}
/>
</div>
</div>
</div>
);
};
ReactDOM.render(<App />, document.getElementById('container'));
I need to add custom dropdown menu in toolbar section.
here attached image similar to want dropdown menu this is possible ?
<img src="https://i.imgur.com/OhYeFsL.png" alt="Dropdown menu editor">
find the detailed image below
I used react-draft-wysiwyg content editor.
https://github.com/jpuri/react-draft-wysiwyg
https://jpuri.github.io/react-draft-wysiwyg/#/d
add custom dropdown menu in toolbar section.
I hope this is still relevant, but here is my way.
For the custom dropdown, I created a new component and used method for "adding new option to the toolbar" from the documentation https://jpuri.github.io/react-draft-wysiwyg/#/docs
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { EditorState, Modifier } from 'draft-js';
class Placeholders extends Component {
static propTypes = {
onChange: PropTypes.func,
editorState: PropTypes.object,
}
state = {
open: false
}
openPlaceholderDropdown = () => this.setState({open: !this.state.open})
addPlaceholder = (placeholder) => {
const { editorState, onChange } = this.props;
const contentState = Modifier.replaceText(
editorState.getCurrentContent(),
editorState.getSelection(),
placeholder,
editorState.getCurrentInlineStyle(),
);
onChange(EditorState.push(editorState, contentState, 'insert-characters'));
}
placeholderOptions = [
{key: "firstName", value: "{{firstName}}", text: "First Name"},
{key: "lastName", value: "{{lastName}}", text: "Last name"},
{key: "company", value: "{{company}}", text: "Company"},
{key: "address", value: "{{address}}", text: "Address"},
{key: "zip", value: "{{zip}}", text: "Zip"},
{key: "city", value: "{{city}}", text: "City"}
]
listItem = this.placeholderOptions.map(item => (
<li
onClick={this.addPlaceholder.bind(this, item.value)}
key={item.key}
className="rdw-dropdownoption-default placeholder-li"
>{item.text}</li>
))
render() {
return (
<div onClick={this.openPlaceholderDropdown} className="rdw-block-wrapper" aria-label="rdw-block-control">
<div className="rdw-dropdown-wrapper rdw-block-dropdown" aria-label="rdw-dropdown">
<div className="rdw-dropdown-selectedtext" title="Placeholders">
<span>Placeholder</span>
<div className={`rdw-dropdown-caretto${this.state.open? "close": "open"}`}></div>
</div>
<ul className={`rdw-dropdown-optionwrapper ${this.state.open? "": "placeholder-ul"}`}>
{this.listItem}
</ul>
</div>
</div>
);
}
}
export default Placeholders;
I used a custom dropdown for adding placeholders. But the essence still stays the same because I use the example from the documentation for a custom button.
To render the button itself I used the same styling, classes, and structure as is used for the other dropdown buttons. I just switched the anchor tag to div tag and added custom classes for hover style and carrot change. I also used events to toggle classes.
.placeholder-ul{
visibility: hidden;
}
.placeholder-li:hover {
background: #F1F1F1;
}
Lastly, don't forget to import and add a custom button to the editor.
<Editor
editorState={this.state.editorState}
onEditorStateChange={this.onEditorStateChange}
toolbarCustomButtons={[<Placeholders />]}
/>
I'v used Tomas his code and updated it a bit to TypeScript / Function components.
Can concur that this solution is still working in 2020 with Draft.js v0.10.5
type ReplacementsProps = {
onChange?: (editorState: EditorState) => void,
editorState: EditorState,
}
export const Replacements = ({onChange, editorState}: ReplacementsProps) => {
const [open, setOpen] = useState<boolean>(false);
const addPlaceholder = (placeholder: string): void => {
const contentState = Modifier.replaceText(
editorState.getCurrentContent(),
editorState.getSelection(),
placeholder,
editorState.getCurrentInlineStyle(),
);
const result = EditorState.push(editorState, contentState, 'insert-characters');
if (onChange) {
onChange(result);
}
};
return (
<div onClick={() => setOpen(!open)} className="rdw-block-wrapper" aria-label="rdw-block-control" role="button" tabIndex={0}>
<div className="rdw-dropdown-wrapper rdw-block-dropdown" aria-label="rdw-dropdown" style={{width: 180}}>
<div className="rdw-dropdown-selectedtext">
<span>YOuR TITLE HERE</span>
<div className={`rdw-dropdown-caretto${open ? 'close' : 'open'}`} />
</div>
<ul className={`rdw-dropdown-optionwrapper ${open ? '' : 'placeholder-ul'}`}>
{placeholderOptions.map(item => (
<li
onClick={() => addPlaceholder(item.value)}
key={item.value}
className="rdw-dropdownoption-default placeholder-li"
>
{item.text}
</li>
))}
</ul>
</div>
</div>
);
};
I am having issues on even trying to get started with doing pagination without the use of any packages. I am pulling data from a JSON file that contains about 30-32 quotes. I need 15 quotes per page to be displayed and have no idea how to even do that using React. So far what I have is all the quotes being displayed by default. I have three buttons, each filters through the JSON to provide quotes by the theme of the quote which is displayed by the button. This is how far I got:
class App extends Component {
constructor(props) {
super(props);
this.state ={
results: quotes,
search: ""
}
}
gameFilterClick = event => {
event.preventDefault();
const games = [];
for(let i = 0; i < quotes.length; i++){
if (quotes[i].theme === "games"){
games.push(quotes[i])
}
}
this.setState({results: games})
}
movieFilterClick = event => {
event.preventDefault();
console.log('blah!!')
const movies = [];
for(let i =0; i < quotes.length; i++){
if(quotes[i].theme === 'movies'){
movies.push(quotes[i])
}
}
this.setState({results: movies})
}
allButtonClick = event => {
this.setState({results: quotes})
}
quoteSearch = query => {
let search = quotes.map
}
render() {
return (
<div className="App">
<h1>Quotes</h1>
<Search />
<div id='buttons'>
Filters:
<button onClick={this.allButtonClick}>All Quotes</button>
<button onClick={this.gameFilterClick}>Games</button>
<button onClick={this.movieFilterClick}>Movies</button>
</div>
<div id='resultsDiv'>
<Results
results={this.state.results}
/>
</div>
</div>
);
}
}
export default App;
I would recommend using react-bootstrap for this. You'll need to install two packages (they use to come in one, but now pagination package is separated):
react-bootstrap-table-next
react-bootstrap-table2-paginator
So, let's install them:
npm i --save react-bootstrap-table-next
npm i react-bootstrap-table2-paginator
And here goes a simple example of implementation:
import BootstrapTable from 'react-bootstrap-table-next';
import paginationFactory from 'react-bootstrap-table2-paginator';
// Let's imagine this is your JSON data
const yourJsonData = [{id: 1, author: "David Goggins", quote: "Life goes on"},
{ id: 2, author: "Robert Green", quote: "yes it does"}]:
// Here we define your columns
const columns = [{
dataField: 'author',
text: 'AUTHOR'
}, {
dataField: 'quote',
text: 'QUOTE'
}];
// Give it an option to show all quotes
let allQuotes = Number(yourJsonData.length);
// Set all of the major pagination options. You can reduce them if you want less
const options = {
paginationSize: 15,
pageStartIndex: 0,
firstPageText: 'First',
prePageText: 'Back',
nextPageText: 'Next',
lastPageText: 'Last',
nextPageTitle: 'First page',
prePageTitle: 'Pre page',
firstPageTitle: 'Next page',
lastPageTitle: 'Last page',
sizePerPageList: [{
text: 'show 15', value: 15
}, {
text: 'show 30', value: 30
}, {
text: 'Show all', value: allQuotes
}]
};
... and then somewhere later in your code where you want to display the table with pagination you just insert this:
<BootstrapTable
keyField='rowNumber'
data={ yourJsonData }
columns={ columns }
pagination={ paginationFactory(options) } />
I hope this solves your problem.
I've simplified your filtering logic and added client side pagination. Check out this simple working example (i've set item per page to 3, you can add more data and change it to 15 const QUOTES_PER_PAGE = <number of quotes per page>;)
const QUOTES_PER_PAGE = 3;
const Quote = ({text}) => <li>{text}</li>;
const Pagination = ({pages, goTo}) => (
<div>
{pages.map((p, i) => (
<button key={i} onClick={goTo} value={i}>{i+1}</button>
))}
</div>
)
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
page: 0,
pagedQuoutes: this.divideQuoutesIntoPages(props.quotes)
};
}
divideQuoutesIntoPages = (quotes => {
const pagedQuotes = [];
[...Array(Math.ceil(quotes.length/QUOTES_PER_PAGE))].forEach((q, i) => {
pagedQuotes.push(quotes.slice(0 + QUOTES_PER_PAGE*i, QUOTES_PER_PAGE + QUOTES_PER_PAGE*i))
})
return pagedQuotes;
})
filterQuoutes = (evt) => {
const filterValue = evt.target.value;
const filteredQuoutes = this.props.quotes.filter(q => !filterValue || q.theme === filterValue);
this.setState({
pagedQuoutes: this.divideQuoutesIntoPages(filteredQuoutes)
})
}
goToPage = (evt) => {
this.setState({
page: evt.target.value
})
}
render() {
return (
<div>
<h1>Quotes</h1>
<div>
Filters:
<button onClick={this.filterQuoutes}>All Quotes</button>
<button onClick={this.filterQuoutes} value="games">Games</button>
<button onClick={this.filterQuoutes} value="movies">Movies</button>
</div>
{this.state.pagedQuoutes[this.state.page]
.map(q => (
<ul>
<Quote {...q} />
</ul>
))}
<Pagination pages={this.state.pagedQuoutes} goTo={this.goToPage} />
</div>
);
}
}
const exampleQuotes = [{
theme: 'games',
text: 'games q1'
}, {
theme: 'games',
text: 'games q2'
}, {
theme: 'games',
text: 'games q3'
}, {
theme: 'games',
text: 'games q4'
}, {
theme: 'games',
text: 'games q5'
}, {
theme: 'movies',
text: 'movies q1'
}, {
theme: 'movies',
text: 'movies q2'
}, {
theme: 'movies',
text: 'movies q3'
}]
ReactDOM.render(<App quotes={exampleQuotes} />, document.getElementById("el"))
<div id="el"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>