I'm having a hard time trying to enable/disable checkboxes using Bootstrap Table Next and React.
For the checkboxes I'm using a column formatter like this:
formatter: (cell, row) => {
return (
<div className='text-right togglebox form-check form-check-inline'>
<input type="checkbox" checked={ row.isInMaintenanceMode } disabled={ row.isReadOnly } className="form-check-input" onChange={(e) => {
row.isReadOnly = true;
if (row.isInMaintenanceMode) {
this.props.update(row.name, { "/redirect" : false });
} else {
this.setState({ isModalOpen: true, server: row.name, isInMaintenanceMode: !row.isInMaintenanceMode });
}
}}
/>
</div>
);
},
Updating the maintenance mode status takes a while and I'd like to disable the checkbox to prevent multiple clicks. Setting row.isReadOnly to true doesn't do anything and no changes are reflected in the column, the checkbox will only be disabled if the isReadOnly property is initially set to true.
Additionally, before setting the maintenance mode to true (NOT the isReadOnly property) the user is presented with a confirmation modal which then calls the update method and then automatically closes, so I'd like to be able to also disable the checkbox from the modal itself.
I've tried using state but I'm not sure it's the right approach as there are potentially hundreds if not thousands of records.
Can anyone tell me what I'm doing wrong?
Thanks
Related
I in my React Application, I have a set of mapped questions from backend data where the user selects a response with a radio button:
<RadioGroup row>
{data.response.map((dt, rIndex) => {
return (
<div className="answerContainer">
<FormControlLabel
className="MuiTypography-body1"
value={dt.value}
onChange={() => {
debugger;
setAnswer(
dt.value,
data.questionTitle,
qIndex,
rIndex
);
}}
checked={selectedAnswers[qIndex] === rIndex}
control={
<Radio
className="PrivateRadioButtonIcon-root-9"
required={true}
/>
}
label={dt.value}
/>
</div>
);
})}
</RadioGroup>
I want to disable the "next" navigation button unless all answers are checked off. I created a state called proceed which by default is false:
const [proceed, setProceed] = React.useState(false);
In my handleChange event if the number of questions is less than the number of answers, the button is disabled.
const setAnswers = async () => {
if (questionsData.length < selectedAnswers.length) {
setProceed(false);
return;
}
Then I added this statement in my setAnswers handleChange function to check if the user can proceed to the next page in the stepper:
if (questionsData.length === selectedAnswers.length) {
setProceed(true);
}
Finally I pass the handleChange function into my button:
<button
className={proceed ? 'enabled' : 'disabled'}
onClick={() => {
setAnswers();
}}
> Next </Button>
The setProceed === false condition works correctly. set proceed === true appeared to be working, but I discovered if I clicked the last answer in the questions without clicking the others, setProceed is === true is triggered and the button is enabled, allowing users to skip the questions.
I added the required flag to the MUI radio button, but the function bypasses it.
Similar to this answer on S/O (which is related to PHP), How can I ensure that all answers must be selected before enabling this state change in React?
Instead of using the length to compare if the all the values are filled . you can use the every from Array.
If your selected Answers contains a list of boolean ( [ true, false ] ) values then you can do
const isAllAnswersSelected = selectedAnswers.every(Boolean)
isAllAnswersSelected will be true if all the items in the selectedAnswers is true else it will return false.
Refer
Array Every
Material UI doesn't provide validations and you must take care of validations and error state yourself.
required form attribute adds the asterisks to the label and not the validations.
<Radio className="PrivateRadioButtonIcon-root-9" required={true} error={questionHasError(qIndex)}/> will again add style level error indicators.
You can maintain a state of answered questions instead?
const [answeredQuestions, setAnsweredQuestions] = useState([]);
.
.
.
const setAnswers = async (qIndex) => {
const updatedAnsweredIdx = [...answeredQuestions, qIndex];
setAnsweredQuestions(updatedAnsweredIdx );
// check if all the questions have been selected/answered
if(allQuestionIndex.every(idx => updatedAnsweredIdx.includes(idx))){
setProceed(true);
} else {
setProceed(false);
}
}
You can then enhance validations by tracking if question is answered and pass error prop to highlight error state.
When trying to create a login form with outlined text fields in Vutify, the chrome autocomplete overlap with labels,
<v-text-field
v-model="email"
label="e-mail"
name="email"
outlined
prepend-icon="mdi-account"
type="text"
required
>
</v-text-field>
you can regeneare here please fill and submit, then go back.
This is how I have fixed this issue.
It seems our main problems are the following:
The autofill from Chrome, at loading page, not made interface react, letting the design like in your image.
So at the time of injection, we should do a fix ourself but no event, from Chrome, can inform us when login/password are auto-filled.
It's interesting to see any click from the browser window FROM USER automatically inform reactivity and all work fine again but it's not work FROM trigger/dispatch internal way.
So first, we need to find a way to react after login/password autofill.
And second, we need to fix ourself the design because only a FROM USER action made the design work fine again.
1. React after autofilling at loading page
Like Harkness mention it, we can try to check :-webkit-autofill at regular interval while X second after code was mounted to see if an autofilling was injected (work fine for Chrome/Firefox/Edge from my test)
Another solution is to use the animationstart event (see here: https://github.com/material-components/material-components-web/issues/4447#issuecomment-580401216)
I use the first solution:
export default {
//...
data() {
return {
//...
autofillFix: false,
}
},
//...
mounted() {
this.autoLoginCheckingInterface()
},
//...
autoLoginCheckingInterface() {
// each 100ms we check if the issue was produced
let intervalDetectAutofill = setInterval(() => {
if (
// we target at least one of the stuff that will be affected by autofill
// to do our checking
document.querySelectorAll('input[type="password"]:-webkit-autofill')
.length > 0
) {
// and we inform the system about the issue if it is produced
this.autofillFix = true
// we stop to check if issue was produced
clearInterval(intervalDetectAutofill)
}
}, 100)
// if after 3s nothing appear, means no autofill was made
setTimeout(() => {
if (intervalDetectAutofill) {
clearInterval(intervalDetectAutofill)
intervalDetectAutofill = null
}
}, 3000)
},
//...
}
<!--
we will inject `.autofill-fix` class to be able fix design ourself at time of this bug occur
-->
<v-text-field
...
:class="{ 'autofill-fix': autofillFix }"
...
label="Email address or username"
...
dense
outlined
#focus="autofillFix = false"
/>
<!--
we use #focus to let the normal behavior take again the lead
because we know this USER ACTION will made Chrome work well again
-->
<v-text-field
...
:class="{ 'autofill-fix': autofillFix }"
...
label="Password"
type="password"
...
dense
outlined
#focus="autofillFix = false"
/>
2. Fix ourself the design
We can see what are the change when v-text-field is filled. Without content, we can see this:
And after autofilling, we can see this:
So from the red part, we can see the following code need to be injected at time
of .autofill-fix presence to fix the design in the proper way
.autofill-fix.v-text-field--outlined.v-input--dense .v-label {
left: -28px!important;
transform: translateY(-16px) scale(.75);
}
Note: You need change the CSS selector if you not use outlined or dense. Be careful about the specificity of selector https://specificity.keegan.st/. In fact, you need adapt the fixed change to your design
Another way is to defined like #elazard suggest here an autofill variable like this
data () {
return {
login: null,
password: null,
autofill: false,
intervalDetectAutofill: null
}
},
<v-text-field
v-model="password"
type="password"
label="Password"
:placeholder="autofill ? ` ` : null"
/>
With the solution given by #adam-reis, in the mounted() of the login page
mounted () {
// search for autofill every 100ms
this.intervalDetectAutofill = setInterval(() => {
if (document.querySelectorAll("input[type=\"password\"]:-webkit-autofill").length > 0) {
this.autofill = true
}
}, 100)
// clean interval if needed after 3s
setTimeout(() => {
if (this.intervalDetectAutofill) {
clearInterval(this.intervalDetectAutofill)
this.intervalDetectAutofill = null
}
}, 3000)
},
And of course setting autofill to false if user input
watch: {
password () {
this.autofill = false
},
autofill () {
// clean interval if autofill detected or user input
if (this.intervalDetectAutofill) {
clearInterval(this.intervalDetectAutofill)
this.intervalDetectAutofill = null
}
}
},
I believe I've achieved a good result with very generic few lines of coding.
mounted() {
setTimeout(() => {
const els = document.querySelectorAll("input:-webkit-autofill")
els.forEach((el) => {
const label = el.parentElement.querySelector("label")
label.classList.add("v-label--active")
})
}, 500)
},
If the browser autofill the v-text-field, this code will add the "active" class to the Label. The v-text-field behaves have no change.
ok so what i did is something like this :
on the input
:placeholder="!autofilled ? ' ' : ''"
in the script
data() {
return {
form: {
email: '',
password: '',
},
error: null,
autofilled: false,
};
},
watch: {
'form.email'() {
this.autofilled = true;
},
},
What it does : basically setting placeholder to one blank space always "raises" the label. the unfortunate side is that setting it statically will make the label unable to go back down even if you empty the input after it is filled. so what i did is make the placeholder dynamic and only set it as a blank space before any change is made to the input after that placeholder goes back to nothing.
it isnt perfect because on initial load before the user has a password saved the labels will be raised but i havent found anything much better than that.
The autofill feature on browsers usually works by straight away setting the value of the fields in question. In this case, the label of the fields, moves out of the way only when the input field is focused, and stays away when it blurs with a value in the field. In case of autofill, the focus event isn't triggered, so the label stays where it is.
To fix this behaviour, you would have to (or get someone to) make changes in Vuetify.
You could give your input an id and read the input's value when the component is mounted and if it's anything else than empty then you could set your data value to that value that the input is holding, that way the label will go up as you would expect. Based on your more recent comments it seems like you would also need to wait for the DOM to be updated so the best thing we can do is to do our check with the help of nextTick:
mounted() {
this.$nextTick(() => {
const emailValue = document.getElementById('email').value;
this.email = emailValue || '';
});
}
I'm stuck in a sutiation where I need to disable the check boxs except the one which is checked.
The checkbox is dynamically created with the help of API and key will be passed to each checkbox. Is there a way to achieve above requirement.
Thanks
{this.state.imageinfo.map((image, key) => {
return(
<div key={key}>
<input type="checkbox"
onClick={(e)=> this.setCoverImage(e,key)}>Cover Image
<div className="delete-image" onClick={() => this.removeImage(key)}>
×
</div>
</div>
);
})
}
You could have that checkbox change a boolean saved in state and have the disabled attribute of all the others equal to that sate object.
use radio instead of checkbox
checkboxes behaviour is multiple selection, while radio isnt
if you want to use checkboxes:
setCoverImage(e, key) {
...
this.setState({active: key})
}
render() {
...
<input type="checkbox"
onClick={(e)=> this.setCoverImage(e,key)} checked={this.state.active === key}>
You are required to set a checked keyword which is boolean in an array which is set in your state. When ever function is called then it will set checked keyword in only that element of Array where the key is provided. Rest of The checked keyword in Arrays will be deleted.and then you will set the Updated Array in your State. Like this.
setCoverImage=(key)=>{
const ItemKeyTobeChecked=key;
const imageinfoArray=this.state.imageinfo;
for(var i=0;i<imageinfoArray.length;i++){
if(key===i){
imageinforArray[key].checked=true;
}
else{
if(imageinforArray[i].checked){
delete imageinforArray[i].checked
}
}
}
this.setState({
imageinfo:imageinfoArray
})
}
I'm trying to create filters in react, where i manipulate the url to return me products based on colours, cost etc
its working so if you change ?color=red to ?color=white in the url it will display different products on the page
it's also working whereby if you select the colours in my checkbox filter component it will update the url and then display the new products. i.e click on red will change the url from /sport to /sport?color=red and then returns me just the products with red
however this is my problem
if I manually change the url, I then want the checkbox checked so I tried to do this:
checked={option.text === this.getParams() ? true : false}
this does actually work but then I lose the ability to actually select and deselect the checkbox. any ideas how I can get it to do both? I guess making it a controlled and uncontrolled component simultaneously??
You need to store the filters in the state. like in your constructor you can init your state with the query parameter and then change the state upon checkbox change.
You could try something like this. You will need to change this code according to your usage, here I am assuming, this.getParams('color') will return an array of all the selected colors.
constructor state init
constructor(props) {
super(props);
this.state = {
filters: this.getParams('color') // consedering, it will return array
}
}
default check the checkbox
defaultChecked ={this.state.filters.indexOf(option.text) === -1 ? false : true}
onChange={() => this.toggleCheckbox(option.text)}
for toggling it
// if not present, then add it
// else remove it
toggleCheckbox(option) {
if (this.state.filters.indexOf(option) === -1) {
this.setState({
filters: [...this.state.filters, option]
})
} else {
this.setState({
filters: this.state.filters.filter(text => text !== option)
})
}
}
You should set the state of the checkbox in the component state, and then update that state when it's clicked. You can set the initial state based on the url on construct or mount.
Something like this:
constructor(props) {
super(props);
const isChecked = this.props.match.params.checkbox === 'true';
this.state = {
checkbox: isChecked
}
}
And then in your checkbox:
<input type="checkbox" checked={this.state.checkbox} onChange={() => this._toggleCheckbox()} />
And the method to turn it on and off would be something like:
toggleCheckbox() {
this.setState({
checkbox: !this.state.checkbox // will toggle to the opposite of the current state
});
}
Note that this is has not been tested but has been written based on the information you gave. The principle behind this is what you need to do. It may also be useful to set the state of the checkbox initially within componentDidMount(), rather than constructor(), but that's up to you. The onChange function of the checkbox uses ES6, but you could bind the function if you prefer or do not use ES6 with this._toggleCheckbox().bind(this)
Edit
To update the checkbox when the url is changed, rather than updating it on click, you could change the toggle method to redirect the browser, and then update the checkbox within componentWillReceiveProps.
Taken from my own code with react-router you can use 'this.props.match.params' to find the url parameters. I use react-router-dom package to update the url. So for instance:
This will give you access to this.props.history.
import { withRouter } from 'react-router-dom';
toggleCheckbox() {
// Check the current state of the checkbox and update the url to the opposite
let toCheck = this.props.match.params.checkbox === 'true' ? 'false' : 'checked';
this.props.history.push('/?checkbox=' + toCheck);
}
componentWillReceiveProps(newProps) {
// Check the new url and update the checkbox if it is different from the checkbox state
if(newProps.match.params.checkbox != this.state.checkbox) {
this.setState({checkbox: newProps.match.params.checkbox});
}
}
I am using react-selectize component for customizable dropdown which allows users to add new options.
<Dropdown
options={myOptions}
value={selectedValue}
onValueChange={value => {
this.valueUpdated(emptyStringToNull(value));
}}
createFromSearch={this.createFromSearch}
/>
My createFromSearch and onValueChange functions are as below;
createFromSearch: function(options, search){
if (search.length === 0 || (options.map(function(option){
return option.label;
})).indexOf(search) > -1)
return null;
else {
return {'label': search, 'value': search};
}
},
onValueChange: function(text) {
// update the value in state
},
Everything works fine other than this small UI issue. It shows duplicate options soon after I click .
When I click anywhere in the screen it removes this duplicate layover and showing properly. Can anyone please suggest is it styling issue or any other thing I need to do?
I able to fix this issue by trying several things. I was overriding onValueChange method of the component and passed only the value to the actual onValueChange method as below;
const onValueChangeInDropdown = props => value => {
if (value) {
props.onValueChange(value.value);
} else {
props.onValueChange(null);
}
};
This cause the above styling issue since component couldn't find out item.newOption attribute. So solution is when adding newly created item for the option list add it as item.newOption = 'true' and pass the whole item object to onValueChange method.