How to deselect row when clicked outside the table? [used: Material-UI DataGrid] - material-ui

I'm using Material-UI tables for showing my data, and I'm stuck when it comes to deselecting selected row.
So when user clicks outside the table, row should be deselected.
This is my code
const showData2 = (e,data) => {
console.log('selection',e)
}
<div style={{ height: 180, width: "100%", backgroundColor:'white' }}>
<DataGrid
rows={cases}
density='compact'
columns={columnsCases}
pageSize={3}
hideFooterSelectedRowCount={true}
rowHeight={40}
onRowSelected = {(e)=>{showData(e)}}
onSelectionChange= {(e)=>{showData2(e)}}
/>
</div>
Selection works perfectly but it seems impossible to deselect row when clicked outside the table
I would appreciate any idea and help.
Thank you!!

Use ClickAwayListener to detect if a click event happened outside of an element. It listens for clicks that occur somewhere in the document.
https://material-ui.com/components/click-away-listener/

Deselect Row Example using the ClickAwayListener with Typescript
Import GridRowId type in addition to whatever other types or components you need from data-grid
import { DataGrid, GridColDef, GridRowId } from '#material-ui/data-grid';
Set local state for the selectionModel prop on the DataGrid and init as an empty array.
const [selectionModel, setSelectionModel] = useState<GridRowId[]>([]);
Add DataGrid component as child of ClickAwayListener... plus your other DataGrid props.
<ClickAwayListener onClickAway={() => setSelectionModel([])}>
<DataGrid
{...otherProps}
checkboxSelection // <= works with or without checkbox selection
selectionModel={selectionModel}
onSelectionModelChange={({ selectionModel }) =>
setSelectionModel(selectionModel)
}
/>
</ClickAwayListener>
Of course you can just remove the types if you're not using Typescript.

Good Day,
Like #Omar EL KHAL said a ClickAwayListener works perfectly.
Step One: Define your onSelectionModelChange and handleClickAway function.
const handleSelection = (newSelection) => {
if (newSelection)
{
setSelectionModel(newSelection.selectionModel);
}
else
{
setSelectionModel(null);
}
}
const handleClickAway = () => {
handleSelection(null);
};
Step Two: Define your selectionModel as State
For this step I chose to set selectionModel in the component state to allow for other functions to set values.
const [selectionModel, setSelectionModel] = React.useState([]);
Step Three: Set the onSelectionModelChange and selectionModel props
Set the props props in the Data Grid as follows.
<ClickAwayListener onClickAway={handleClickAway}>
<DataGrid
onSelectionModelChange={handleSelection}
selectionModel={eventSelectionModel}
/>
</ClickAwayListener>
Once you save and let NPM do its thing you it should work.
I personally tested this on a single selection model but it should work with multiple as well.
P.S. I also did this with functional components and not classed components. The theory would be the same just instead of using useState you would use this.setState.
Happy Coding!
Gunny

There is no need for extra state. You could simply call the setSelectionModel function on the ClickAwayListener like so:
<ClickAwayListener onClickAway={() => apiRef.current.setSelectionModel([])}>
// your Data Grid
</ClickAwayListener>

Related

Material UI IconButton onClick doesn't let to handle event

I installed "#material-ui/core": "^4.9.2" and "#material-ui/icons": "^4.9.1".
In my form i have several rows, each row has an add button and a remove button. I want the remove button to remove the row from it was clicked. It works fine with regular Button with a "-" character in it. But i want it fancy, so i replaced my Button from an IconButton, and imported the icons to use
import {AddCircleOutline,RemoveCircleOutlineOutlined} from "#material-ui/icons";
And my IconButton looks like this:
<IconButton
onClick={props.onRemoveClick}
className="align-self-center"
color="info"
size="sm"
disabled={props.index > 0 ? false : true}
<RemoveCircleOutlineOutlined/>
</IconButton>
When the IconButton is hit, the onClick method is called (i know because of logs in my console) but i can't handle the event because it is now undefined.
The funny thing is that if i click on the button area that doesn't correspond to the icon, it works. But obviously i need it to work in the whole area of the button.
It is not a binding issue because i already tested it.
Any ideas?
Props that are not cited in the documentation are inherited to their internal <EnhancedButton />, so you need to use a wrapper.
<IconButton
onClick={(e) => props.onRemoveClick(e)}
className="align-self-center"
color="info"
size="sm"
disabled={props.index > 0 ? false : true}
<RemoveCircleOutlineOutlined/>
</IconButton>
Well you gave an idea. Since i needed an index to identify the row's button, i sended the index through a paramater on the onClick method, like this:
onClick={e => props.onRemoveClick(props.index)}
In this way i didn't need to handle the event. I also had to bind my method on the constructor:
constructor(props) {
super(props);
this.handleRemoveClick = this.handleRemoveClick.bind(this);
}
Now i got the behaviour wanted
You can see the github ussue here. There is some problem with typescript definition files but we can work around it.
Solution
I tried to solve it like in the github issue but didn't work. So this works for me.
const onClick = (e: any) => {
// e is of type any so the compiler won't yell at you
}
<IconButton onClick={(e) => onClick(e)}>
I don't know the reason but using e.currentTarget helped me to get the button that I wanted and not the material icon inside it.
onClick={(e) => {
return console.log(e.currentTarget)
}}

How could I add custom row focus class in ag-grid

I want to control row focus process. I need to show confirm dialog on row focus change in my table.
I tried to do this with rowClassRules property, but as I understood that functionality apply classes when table rendering, after that row classes stop changing
rowClassRules = {
'custom-row-focus': (params) => {
return params.data.id === this.currentSelectedItem.id
}
}
currentSelectedItem set's when I click on the row
Found an answer in docs
https://www.ag-grid.com/javascript-grid-row-styles/#refresh-of-styles
If you refresh a row, or a cell is updated due to editing, the rowStyle, rowClass and rowClassRules are all applied again.
So, when I'm clicking to the row I should make something like that:
onClicked($event: RowClickedEvent) {
$event.node.setData({...$event.data});
}

Set initial value to select within custom component in Angular 4

As you can see in this plunkr (https://plnkr.co/edit/3EDk5xxSLRolv2t9br84?p=preview) I have two selects: one in the main component behaving as usual, and one in a custom component, inheriting the ngModel settings.
The following code links the innerNgModel to the component ngModel.
ngAfterViewInit() {
//First set the valueAccessor of the outerNgModel
this.ngModel.valueAccessor = this.innerNgModel.valueAccessor;
//Set the innerNgModel to the outerNgModel
//This will copy all properties like validators, change-events etc.
this.innerNgModel = this.ngModel;
}
It works, since the name property is updated by both selects.
However when it first loads the second select has no selection.
I guess I'm missing something, a way to initialize the innerNgModel with the initial value.
This is a weird situation to do something like this, but I believe to get this working they need to implement another life-cycle hook. AfterModelSet or something like that :)
Anyways, you can solve this with a simple setTimeout and a setValue:
ngAfterViewInit() {
this.ngModel.valueAccessor = this.innerNgModel.valueAccessor;
this.innerNgModel = this.ngModel;
setTimeout(() => {
this.innerNgModel.control.setValue(this.ngModel.model);
})
}
plunkr

ClipboardJS with React, using document.getElementById()

Originally, I had it working fine.
Then I did this and now I can't get it to work
ClipboardField.js
import React from 'react';
export default (props) => {
return(
<div id="clip" data-clipboard-text={props.code} onClick={props.onClick}>
<p> Copy to clipboard.</p>
</div>
);
}
Field.js
class DashWizardTwoCore extends Component {
componentDidMount(){
const btns = document.getElementById('clip');
const clipboard = new Clipboard(btns);
}
componentDidUpdate() {
clipboard.on('success', function(e) {
console.log(e);
});
clipboard.on('error', function(e) {
console.log(e);
});
}
render(){
const someCode = "const foo = 1"
return (
<div>
<ClipboardField code={this.someCode} /> }
</div>
);
}
}
If you take the code out of ClipboardField and into Field it works. It's mostly, how do I use document.getElementById() in my parent component to find something in my child?
Their examples:
https://github.com/zenorocha/clipboard.js/blob/master/demo/constructor-selector.html#L18
https://github.com/zenorocha/clipboard.js/blob/master/demo/constructor-node.html#L16-L17
https://github.com/zenorocha/clipboard.js/blob/master/demo/constructor-nodelist.html#L18-L19
Your code is fine you just have a few issues:
you are binding clipboard.on in componentDidUpdate which won't trigger here since you are not really changing anything (in the ClipboardField component) that triggers this event.
You are passing {this.someCode} in the code prop which would be undefined should just be {someCode}
So it's just a matter of moving your clipboard.on to the componentDidMount right after the new Clipboard and use code={someCode}
https://jsfiddle.net/yy8cybLq/
--
In React whenever you want to access the actual dom element of your component we use what react calls as refs, I would suggest you do this rather than using getElementById as this is the "best practice".
However stateless components (like your ClipboardField component above) can't have refs so you just need to change it to be a normal component.
Here's a fiddle with your code but using refs instead: https://jsfiddle.net/e5wqk2a2/
I tried including links to the react docs for stateless components and refs but apparently don't have enough "rep" to post more than 2 links, anyways quick google search should point you in the right direction.
I adjusted your code and created a simple integration of clipboard.js with React.
Here's the fiddle: https://jsfiddle.net/mrlew/ehrbvkc1/13/ . Check it out.

Is it fine to mutate attributes of React-controlled DOM elements directly?

I'd like to use headroom.js with React. Headroom.js docs say:
At it's most basic headroom.js simply adds and removes CSS classes from an element in response to a scroll event.
Would it be fine to use it directly with elements controlled by React? I know that React fails badly when the DOM structure is mutated, but modifying just attributes should be fine. Is this really so? Could you show me some place in official documentation saying that it's recommended or not?
Side note: I know about react-headroom, but I'd like to use the original headroom.js instead.
EDIT: I just tried it, and it seems to work. I still don't know if it will be a good idea on the long run.
If React tries to reconcile any of the attributes you change, things will break. Here's an example:
class Application extends React.Component {
constructor() {
super();
this.state = {
classes: ["blue", "bold"]
}
}
componentDidMount() {
setTimeout(() => {
console.log("modifying state");
this.setState({
classes: this.state.classes.concat(["big"])
});
}, 2000)
}
render() {
return (
<div id="test" className={this.state.classes.join(" ")}>Hello!</div>
)
}
}
ReactDOM.render(<Application />, document.getElementById("app"), () => {
setTimeout(() => {
console.log("Adding a class manually");
const el = document.getElementById("test");
if (el.classList)
el.classList.add("grayBg");
else
el.className += ' grayBg';
}, 1000)
});
And here's the demo: https://jsbin.com/fadubo/edit?js,output
We start off with a component that has the classes blue and bold based on its state. After a second, we add the grayBg class without using React. After another second, the component sets its state so that the component has the classes blue, bold, and big, and the grayBg class is lost.
Since the DOM reconciliation strategy is a black box, it's difficult to say, "Okay, my use case will work as long as React doesn't define any classes." For example, React might decide it's better to use innerHTML to apply a large list of changes rather than setting attributes individually.
In general, if you need to do manual DOM manipulation of a React component, the best strategy is to wrap the manual operation or plugin in its own component that it can 100% control. See this post on Wrapping DOM Libs for one such example.