vue.js force update input element not working - javascript

I have an input element that bind with v-model to a item value, I want to limit the user input to just type numeric value on range between 0 to 10, I was tried this thing before(add #input and check the input value to keep it in range)
my code is like this:
<v-text-field #input="checkItem" v-model="item"></v-text-field>
checkItem(val) {
if(parseInt(val) < 1) {
this.item = 1;
} else if(parseInt(val) >10) {
this.item = 10;
}
}
Problem
after first time we type number out of range the function works great and keep it in range but when we type out of range number again the element didn't update because the new item value is the same as the old item value! to solve this I try to use forceUpdate and the $forceUpdate() not work!!!
for example
if user type anything between range number into input, because it's in range everything is ok;
but if user type 0 or any number outside of range, on the first time item value change to 1 if value was under 1 but if again type any negative value because the item value last time was changed to 1 when we set it to 1 again nothing happening on the view and the element value was not updated.
The main question is how to force vue to update this input field value?

<div><input type="number" v-model="item"></input></div>
</template>
<script>
export default {
name: "ranges",
data() {
return {
item: Number,
error: String
};
},
watch: {
item(newVal, lastVal) {
if (newVal > 10) this.item = 10
if (newVal < 1) this.item = 1
}
}
};
</script>
Here using the watcher you can do that validation

The only way to force the reactivity when the the final result is always the same, is to re-render the component for it to reflect the changes by updating its key.
Reference link here.
I have forked the sample Vue project from mdiaz00147, and modify into
this, and I think it works as the author intended it to be.
Solution Code modified from mdiaz00147 's code snippet
<template>
<div>
<input :key="inputKey" v-model="item" #change="checkItem" />
</div>
</template>
<script>
export default {
name: "ranges",
data() {
return {
item: null,
inputKey: 0,
};
},
methods: {
checkItem() {
if (parseInt(this.item) < 1) {
this.item = 1;
} else if (parseInt(this.item) > 10) {
this.item = 10;
}
this.inputKey += 1;
},
},
};
</script>

Related

How to run a function to set height on all HTML tags of a particular type?

I have some conditional renders of textarea elements in many places on a form that shows/hides these elements depending on what the user is doing. For example:
<li v-if="Form.Type === 1">
<textarea v-model="Form.Title" ref="RefTitle"></textarea>
</li>
There could be any number of textarea elements like above. What I need to do is resize these elements at certain points in the lifecycle (e.g. onMounted, onUpdated).
The function that gets triggered to do this is:
setup() {
...
const RefTitle = ref(); // This is the ref element in the template
function autosizeTextarea() {
RefTitle.value.style.height = "35px"; // Default if value is empty
RefTitle.value.style.height = `${RefTitle.value.scrollHeight}px`;
}
...
}
In the code above I am specifically targeting a known textarea by its ref value of RefTitle. I could test for its existence using an if(RefTitle.value) statement.
But how could I get all the textarea elements that may be rendered and then run autosizeTextarea on all of them?
I can get all the textarea elements like such:
setup() {
...
function autosizeTextarea() {
const AllTextareas = document.getElementsByTagName('TEXTAREA');
for (let i=0; i < AllTextareas.length; i++) {
// How can I set the style.height = `${RefTitle.value.scrollHeight}px`;
// in here for each element?
}
}
...
}
But how can style.height be set on all of them?
You could create your own custom component representing a textarea with the functionality in the component itself, so you don't have to get all textareas which are dynamically created.
It could look something like this:
<template>
<textarea :value="modelValue" #input="$emit('update:modelValue', $event.target.value)" ref="textarea" :style="styleObject"></textarea>
</template>
<script>
export default {
emits: {
'update:modelValue': null,
},
props: {
modelValue: {
type: String,
},
// Prop for dynamic styling
defaultHeight: {
type: Number,
required: false,
default: 35,
validator(val) {
// Custom Validator to ensure that there are no crazy values
return val > 10 && val < 100;
}
},
computed: {
styleObject() {
return {
height: this.$refs['textarea'].value.scrollHeight ? `${this.$refs['textarea'].value.scrollHeight}px` : `${this.defaultHeight}px`,
}
},
</script>
That way you can even use v-model on it.
<li v-if="Form.Type === 1">
<custom-textarea v-model="Form.Title" :defaultHeight="45"></textarea>
</li>
The Template I provided is just to show you how a custom component could look like. You might have to fit it into your logic depending on when you actually want things to change/trigger.
I have managed to do it like this:
const AllTextareas = ref(document.getElementsByTagName("TEXTAREA")); //returns an object not an array
for (const [key, value] of Object.entries(AllTextareas.value)) {
AllTextareas.value[key].style.height = AllTextareas.value[key].scrollHeight ? `${AllTextareas.value[key].scrollHeight}px` : "35px";
}

Svelte3 input validation

Yesterday I was trying to translate a problem I had to solve in React, to Svelte, and I can't figure it out.
The problem is the following:
I have 3 inputs each one of them holds a percentage.
The 3 percentages altogether cannot add more than 100.
I have a fourth input, it's disabled, so it just shows the remaining percentage to 100%
In react is fairly easy, declare a function that takes, the event and a variable to know which input I'm taking the event from. Do the proper validations and done.
Sadly in svelte I have almost 0 experience, and I don't know how to tackle it.
This the code so far (spoiler alert it does not even get close to do what is supposed to do).
Svelte REPL
Running a console.log to show the value of sp1, inside the function that validates it, and outside the function (before and after the function), shows what I do expect:
Before the function (before): value in the input
Inside the function: value validated
Outside the function (after): value validated
So the validation and assignment of the correct value takes place, nonetheless the input keep showing wrong value (ex: input shows 112 and the value should be 100).
A more dynamic way to do this would be using arrays.
let inputs = [{
value: 0,
color: 'GRAY'
}, {
value: 0,
color: 'DARKSLATEGRAY'
}, {
value: 0,
color: 'TEAL'
}];
This would also make calculations easier since we can use the array methods. It would also enable us to use each blocks which remove redundant code. Then we will use a function called validate and pass the index of the current input to it. Using the input we can calculate the maximum possible value that can be entered in that input and set the input if it crosses that maximum value. This validate function will be called from the template on the input event.
Here's the code.
<script>
let total = 100;
let inputs = [{
value: 0,
color: 'GRAY'
}, {
value: 0,
color: 'DARKSLATEGRAY'
}, {
value: 0,
color: 'TEAL'
}];
$: spOthers = total - inputs.map(x => x.value || 0).reduce((a, b) => a + b);
function validate(index) {
let maxInput = total - inputs.map(x => x.value).filter((x, i) => i !== index).reduce((a, b) => a + b);
if (inputs[index].value > maxInput) {
inputs[index].value = maxInput;
}
}
</script>
{#each inputs as {value, color}, i}
<div class='input-container'>
<div class='square' style="background-color: {color}" />
<input type="number" min="0" class='input' bind:value={value} on:input={() => validate(i)} />
</div>
{/each}
<div class='input-container'>
<div class='square' style="background-color: DARKORANGE" />
<input type='number' class='input input-others' bind:value={spOthers} disabled/>
</div>
Note: I have omitted the styles above as there are no changes in them.
Here is a working REPL.
Nice. You can also do:
$: spOthers = inputs.reduce((a, c, i, inputs) => a + c.value, 0);
function validate(index) {
const delta = spOthers - total;
if (delta > 0) {
inputs[index].value -= delta;
}
}

vuejs Computed data detecting if model has been updated

I'm trying to detect if a Model Data has not been changed within a Vue Computed data.
I have two sets of Variables that need to be checked,
before Computed:filteredItems should return a new list or current list.
Below are two data i'm checking
text ( the text input )
selectedInput ( currently selected item )
Current Behavior:
I've changed, selectedInput to null, this updates Computed:filteredList to be triggered. which is expected.
The first Condition is to make sure that this update returns current list if text === selectedInput.text, work as expected
However, I need a second condition to detect if text has not been changed.
<input v-model="text" />
<ul>
<li v-for="item in filteredItems" #click="text=item.text"></li>
</ul>
{
data():{
text: 1,
items: [],
tempList: [],
selectedItem: {text: 1}
},
computed: {
filteredItems(){
// when selectedItem.text === current text input, do not run
if (this.selectedItem.text === text) return this.tempList;
// how do i detect if selectedItem.text has not been changed
if (this.selectedItem.text.hasNotChange??) return this.tempList;
}
}
}
Data Flow: 1update the text > 2filter list > 3click on listItem, update (1) text
[input(text): update on type ] >
[li(filteredItem): filter list on type by value (text) and (selectedInput.text) ] >
[li(item)#click: update (1), and also another value(selectedInput.text) input(text) to equal (item.text) ]
This cycle works until I have action somewhere else that updates selectedInput.text
is there something i can do with a setter/getter for the Text model.
Create a variable, changed. Watch selectedItem.text, and set changed to true. In a watcher on text, set changed to false.
I got this to work using a temp variable
data(){
return: {
text: "",
temp: {
text
}
}
}
computed(){
filteredList(){
var temporaryList,originalList,filteredList
if ((this.text === $store.state.selectedText )||
(this.text === this.temp.text ) ) {
return temporaryList || originalList
}
// update
this.temp.text = this.text
return filteredList
}
}
thought it would be a bad practice to update variables within a Computed method.

Function for changing numbers with React doesn't work properly

I'm trying to change number stored in a variable by clicking a button but the first time I click the button, it doesn't change the value of the variable but the second one does. I change the number in increments of 1, so when I click the button its currentNumber += 1 and I run a console.log after it to see if it changes. The first time I click it, it prints the default value, and the second time that I click it is when it actually changes, and it's messing up the intended functionality of my code. I'm using React for this.
constructor(props) {
super(props);
this.state = {
currentSize: parseInt(this.props.size),
min: 4,
max: 40,
}
};
increaseSize(){
this.setState({currentSize: this.state.currentSize + 1}, function(){
if(this.state.currentSize >= this.state.max){
this.state.currentSize = this.state.max;
this.state.isRed = true;
} else if(this.state.currentSize < this.state.max){
this.state.isRed = false;
}
});
console.log(this.state.currentSize);
};
render() {
var isBold = this.state.bold ? 'normal' : 'bold';
var currentSize = this.state.currentSize;
var textColor = this.state.isRed ? 'red' : 'black';
return(
<div>
<button id="decreaseButton" hidden='true' onClick={this.decreaseSize.bind(this)}>-</button>
<span id="fontSizeSpan" hidden='true' style={{color: textColor}}>{currentSize}</span>
<button id="increaseButton" hidden='true' onClick={this.increaseSize.bind(this)}>+</button>
<span id="textSpan" style={{fontWeight: isBold, fontSize: currentSize}} onClick={this.showElements.bind(this)}>{this.props.text}</span>
</div>
);
}
The number in the variable is then displayed but the one being displayed has a different value to the one inside the variable
As you can see in the picture, the number displayed is 26 but in the variable its 25.
Additionally, you can see that I set a min and max value for the counter. When it reaches either value, it goes 1 further in the display, but not in the console. So in the display it stops at 3 and 41 but in the console it stops at 4 and 40.
What am I doing wrong?
edit: the default value is 16, and that's whats printed to the console the first time I click the button, which is why its not working properly.
Use the functional version of setState() in order to get a hold of prev state values -- because React handles state changes asynchronously, you can't guarantee their values when you set them; This is also the cause where you are using the console.log. Instead go for something like:
increaseSize(){
const aboveMax = this.state.currentSize >= this.state.max;
this.setState( prevState => ({
currentSize: aboveMax ? prevState.max : prevState.currentSize + 1,
isRed: aboveMax
})
, () => console.log(this.state.currentSize) );
};
Or, move the console statement to the render() method if you don't want to the setState() callback function.
See https://reactjs.org/docs/react-component.html#setstate
Don't forget to set isRed in your constructor as well :)

How to color an input box depending on the size of varible in ReactJs?

I have an input box which is disabled. It gets a variable x which is sometimes < 1 or > 1. I want to color the background with red if x > 1, green if x < 1 and greyas a default (without a value).
I tried this:
<Field name="testValue" type="number" component={Input} label="Value:"
onChange={this.changeColor()} id="test"/>
the method changeColor():
changeColor() {
let num = document.getElementById("test");
if(myValue === "") {
num.css("backgroundColor", "grey");
} else if(myValue < 1) {
num.css("backgroundColor", "green");
} else {
num.css("backgroundColor", "red");
}
}
and in the constructor: this.changeColor = this.changeColor.bind(this);
Unfortunately I am getting: Cannot read property 'css' of null
What I am doing wrong? Is it the correct way? Any help or suggestions is very appreciate.
onChange expect a function but you are assigning the value returned by the function, instead of that write it like this remove ():
<Field
name="testValue"
type="number"
component={Input}
label="Value:"
id="test"
onChange={this.changeColor}
/>
But i will suggest you to use these conditions on style tag instead of using document.getElementById('text') then applying css on that, we should avoid the direct dom manipulation with react.
Store the value of input field in state variable myValue then write it Like this:
changeBGColor() {
let myValue = this.state.myValue;
if(myValue === "") {
return "grey";
} else if(myValue < 1) {
return "green";
} else {
return "red";
}
}
<Field
name="testValue"
type="number"
style={{backgroundColor: this.changeBGColor()}}
component={Input}
label="Value:"
id="test"
onChange={this.changeColor}
/>
You mentioned that you used React to render your component. So you should keep in mind, if you are building a React app, that you should not use the document anywhere in your code.
Anyway, to answer your question, if you need to render differently according to some value, in react, that value should live in the state of your component.
In the changeColor callback, you should read the value of the input and write it to the state of the component:
changeColor(event) {
// event.target is the dom element that emitted the event
this.setState({ inputValue: event.target.value });
}
And then, in your render function, you just give the color to the input depending on the value of the state:
render() {
var fieldColor = "red";
if (this.state.inputValue === "") {
fieldColor = "grey";
} else if (this.state.inputValue < 1) {
fieldColor = "green";
}
<Field style={ color: fieldColor } onChange={this.changeColor}/>
}
This way your render depends on the state of the component and the color updates as the user types in.
Now I'd suggest changing the name of the method changeColor to something more relevant to what the new function is doing, something like updateTestValue maybe?

Categories