Vue js, detect when a value changed to highlight its text - javascript

I have this simple a script that generate random number every few moments, everytime rand is not equals to the one before i want to change its backgound-color. possible?
So the random number generates 1,1,3 when it gets to 3 i want to hightlight the background. thanks
https://jsfiddle.net/keseyxgm/1/
new Vue({
el: '#app',
data: {
rand: 0
},
mounted : function(){
var me = this;
setInterval(function(){
me.rand = Math.floor(Math.random() * 4) + 1 ;
me.$forceUpdate();
},1000)
}
})
<div id="app">
<p>{{rand}}</p>
</div>

Make a data property to store whether the updated value is different from the current value and bind the background-color to that:
new Vue({
el: '#app',
data() {
return {
rand: 0,
diff: false
}
},
mounted() {
setInterval(() => {
let rand = Math.floor(Math.random() * 4) + 1 ;
this.diff = rand !== this.rand;
this.rand = rand;
}, 1000);
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.2/vue.min.js"></script>
<div id="app">
<p :style="{ 'background-color': (diff) ? 'gold' : 'initial' }">{{rand}}</p>
</div>

A nice approach is to use a watcher with a class binding.
See this js fiddle: https://jsfiddle.net/omarjebari/wjf8qbt0/18/
<div id="app">
<div class="random-element" v-bind:class="{ active: isChanged }">{{ rand }}</div>
</div>
<script>
new Vue({
el: "#app",
data: {
rand: 0,
isChanged: false,
},
mounted : function(){
setInterval(() => {
this.rand = Math.floor(Math.random() * 4) + 1;
}, 1000);
},
watch: {
rand(newVal, oldVal) {
console.log(`${newVal} vs ${oldVal}`);
if (newVal !== oldVal) {
this.isChanged = true;
setTimeout(() => {
this.isChanged = false;
}, 300);
}
}
},
})
</script>
<style>
div.random-element {
height: 30px;
color: black;
padding: 5px;
}
div.active {
background-color: red;
}
</style>

Related

How to change the color/size/etc of dynamically created buttons in javascript?

I have created dynamic buttons in vuejs where each button represents a different answer to a question.
My goal is: when I get the answer wrong, the correct option is highlighted in green until the next question is shown.
Is it also possible to change other settings of these "BaseButtons" with CSS? How can I do this?
<template>
<div class="container-botoes">
<BaseButton class="optionsButtons"
v-for="options in optionsAnswers"
:key="options.id" #click="handleAnswer(options)">
{{options.ans}}
</BaseButton>
</div>
</template>
methods:{
handleAnswer(options){
if (options.id === this.correctAnswer){
this.playerHit = true;
}
else {
this.opponentHit = true;
}
this.nextquestion();
},
One option is to create css classes with styles you need and append them to BaseButton component depending on your conditions
Have a look at this one:
HTML block:
<template>
<div class="container-botoes">
<BaseButton
v-for="(options, index) in optionsAnswers"
:key="options.id"
class="optionsButtons"
:class="correctAnsIndex === index ? 'green-button' : 'red-button'"
#click="handleAnswer(options, index)"
>
{{ options.ans }}
</BaseButton>
</div>
</template>
JavaScript block:
<script>
export default {
data() {
return {
correctAnsIndex: null,
}
},
methods: {
handleAnswer(options, index) {
if (options.id === this.correctAnswer) {
this.playerHit = true
this.correctAnsIndex = index
} else {
this.opponentHit = true
this.correctAnsIndex = null
}
this.nextquestion()
},
},
}
</script>
CSS block:
<style>
.red-button {
background: red;
color: white;
font-weight: 700;
}
.green-button {
background: green;
color: white;
font-weight: 700;
}
</style>
Code explanation:
We have passed the index of the loop in the handleAnswer method, where the value of the index will be assigned to the correctAnsIndex variable if options.id === this.correctAnswer and in the else part we will assign null value to the correctAnsIndex variable.
Now, we have applied conditional classes in HTML block, where if the index and correctAnsIndex matches then it would apply green-button class or else it will apple red-button class.
Eventually getting your expected result.
Try this :
Vue.component('basebutton', {
data() {
return {
isCorrect: false
}
},
props: ['answerobj'],
template: `<button :class="{ 'green': isCorrect, 'white': !isCorrect}" #click="handleAnswer(answerobj)">{{ answerobj.answer }}</button>`,
methods: {
handleAnswer(answerobj) {
if (answerobj.correct) {
this.isCorrect = true
} else {
this.isCorrect = false
}
}
}
});
var app = new Vue({
el: '#app',
data: {
list: [{
question: 'Who is the tallest animal ?',
optionsAnswers: [{
answer: 'Elephant',
correct: false
}, {
answer: 'Jirafe',
correct: true
}, {
answer: 'Lion',
correct: false
}, {
answer: 'Zebra',
correct: false
}]
}]
}
});
.green {
background-color: green;
}
.white {
background-color: white;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<div v-for="(item, index) in list" :key="index">
<p><strong>Question : </strong>{{ item.question }}</p>
<p><strong>Answers :</strong></p>
<BaseButton v-for="(options, i) in item.optionsAnswers" :key="i" :answerobj="options">
</BaseButton>
</div>
</div>

Use a variable defined in a method inside the template

it's the first time I use Vue (v2 not v3) and I'm stucked trying to use a variable (defined inside a methods) inside the template.
My semplified code:
<template>
<div class="container" #mouseover="isHovered = true" #mouseleave="isHovered = false">
<div class="c-container">
<div ref="topCContainerRef" class="top-c-container">
<div
:class="['top-c', ...]"
:style="{ height: `${isHovered ? 0 : this.scaledHeight}` }" // <-- HERE I need `scaledHeight`
>
</div>
</div>
</div>
</div>
</template>
<script>
import { scaleLinear } from 'd3-scale'
export default {
name: 'MyComponent',
components: { },
props: {
...,
datum: {
type: Number,
required: true,
},
...
},
data: function () {
return {
isHovered: false,
scaledHeight: {},
}
},
mounted() {
this.matchHeight()
},
methods: {
matchHeight() {
const topCContainerHeight = this.$refs.topCContainerRef.clientHeight
const heightScale = scaleLinear([0, 100], [20, topCContainerHeight])
const scaledHeight = heightScale(this.datum)
this.scaledHeight = scaledHeight // I want to use this value inside the template
},
},
}
</script>
How can I get the value of scaledHeight inside the template section?
If I didn't use this, I get no error but the height value is always 0, like scaledHeight is ignored..
I read the documentation but it doesn't help me
I encountered and solved this problem today.
You can change your styles like below.
<div
:class="['top-c', ...]"
:style="{ height: isHovered ? 0 : scaledHeight }"
>
It works fine for me, and hope it will help you~~
Fixed using computed
computed: {
computedHeight: function () {
return this.isHovered ? 0 : this.matchHeight()
},
},
methods: {
matchHeight() {
const topCContainerHeight = this.$refs.topCContainerRef.clientHeight
const heightScale = scaleLinear([0, 100], [20, topCContainerHeight])
return heightScale(this.datum)
},
},

How do I set timeout after button has been clicked in vue

I need a background colour to go back to its default colour after 3 seconds, I am using the setTimeout method but it is not working, how do I properly use it in this situation? or do I use a transition
<div id="exercise">
<div>
<p>Current Value: {{ value }}</p>
<button #click="value += 5(); red();" :style="{ 'background-color': color }">Add 5</button>
<button #click="value += 1">Add 1</button>
<p>{{ result }}</p>
</div>
<div>
<input type="text" v-model="timer">
<p>{{ value }}</p>
</div>
</div>
new Vue({
el: "#exercise",
data: {
value: 0,
timer: 1000,
color:'pink',
},
methods:{
red() {
this.color = "red";
setTimeout(function() {
this.red = 0;
}, 1000);
}
},
computed: {
result: function() {
return this.value >= 37 ? "Not there yet" : "done";
}
},
watch: {
result: function(value) {
var vm = this;
console.log(value);
setTimeout(function() {
vm.value = 0;
}, 5000);
}
}
});
Try to use an arrow function for your setTimeout, as the setTimeout points the this to the global object. Arrow functions use lexical scoping and knows to bind the this to the inner function:
new Vue({
el: "#exercise",
data: {
value: 0,
timer: 1000,
color:'pink',
},
methods:{
red() {
this.color = "red";
setTimeout(() => {
this.color = "";
}, 1000);
}
},
computed: {
result: function() {
return this.value >= 37 ? "Not there yet" : "done";
}
},
watch: {
result: function(value) {
var vm = this;
console.log(value);
setTimeout(function() {
vm.value = 0;
}, 5000);
}
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="exercise">
<div>
<p>Current Value: {{ value }}</p>
<button #click="red();" :style="{ 'background-color': color }">Add 5</button>
<button #click="value += 1">Add 1</button>
<p>{{ this.result }}</p>
</div>
<div>
<input type="text" v-model="timer">
<p>{{ value }}</p>
</div>
</div>
(Also, inside of your setTimeout, you were trying to change this.red = 0, when it should be this.color = "" :D

How to get height of the just removed element from Vue.js DOM

I have a list of items in Vue.js from index 0 to 49
I show a subset of these items controlled by startIndex and endIndex
When I increment startIndex to 1, items from 1-49 are shown, 0 is removed from DOM
How to get height of 0 that was just removed
Also how to get height of item that was just added if I edit the endIndex?
HTML
<script type="text/x-template" id="virtual-list">
<div id="root" ref="root">
<div id="viewport" ref="viewport">
<div id="spacer" ref="spacer" :style="spacerStyle">
<div v-for="i in visibleItems" :key="i.index" :id="i.index" class="list-item">
{{i.value}}
</div>
</div>
</div>
</div>
</script>
<div id="app">
<button #click="incrementStart">Start +</button>
<button #click="decrementStart">Start -</button>
<button #click="incrementEnd">End +</button>
<button #click="decrementEnd">End -</button>
<virtual-list></virtual-list>
</div>
CSS
* {
box-sizing: border-box;
}
html,
body,
#app {
height: 100%;
}
#app {
padding: 1.25rem;
}
#root {
height: 50%;
overflow-y: auto;
}
.list-item {
padding: 0.75rem 0;
}
Vue.js
const PAGE_SIZE = 50;
const items = new Array(PAGE_SIZE).fill(null).map((item, index) => {
return {
id: faker.random.uuid(),
index: index,
value: "Item " + index + " " + faker.random.words(index % 25)
};
});
const bus = new Vue({});
Vue.component("virtual-list", {
template: "#virtual-list",
data() {
return {
isMounted: false,
items,
startIndex: 0,
endIndex: PAGE_SIZE,
scrollTop: 0,
translateY: 0,
scrollDirection: 0
};
},
computed: {
visibleItems() {
return this.items.slice(this.startIndex, this.endIndex);
},
/**
Translate the spacer verticaly to keep the scrollbar intact
We only show N items at a time so the scrollbar would get affected if we dont translate
*/
spacerStyle() {
return {
willChange: "auto",
transform: "translateY(" + this.translateY + "px)"
};
}
},
methods: {
handleScroll() {
this.scrollTop = this.$el.scrollTop;
this.startIndex = Math.floor(this.scrollTop / 42);
}
},
watch: {
scrollTop(newValue, oldValue) {
if (newValue > oldValue) {
this.scrollDirection = 1;
} else if (newValue < oldValue) {
this.scrollDirection = -1;
}
},
startIndex(newValue, oldValue) {
// console.log(this.$refs.spacer.children);
}
},
beforeUpdate() {
// console.log('before update', this.$refs.spacer.children);
},
mounted() {
this.isMounted = true;
const children = this.$refs.spacer.children;
for (let i = 0; i < children.length; i++) {
// console.log(children[i].offsetTop - this.$el.offsetTop);
children[i].setAttribute("data-height", children[i].scrollHeight);
}
bus.$on("incrementStart", () => {
this.startIndex++;
});
bus.$on("decrementStart", () => {
this.startIndex--;
});
bus.$on("incrementEnd", () => {
this.endIndex++;
});
bus.$on("decrementEnd", () => {
this.endIndex--;
});
this.$el.addEventListener("scroll", this.handleScroll);
},
destroyed() {
this.$el.removeEventListener("scroll", this.handleScroll);
}
});
new Vue({
el: "#app",
methods: {
incrementStart() {
bus.$emit("incrementStart");
},
decrementStart() {
bus.$emit("decrementStart");
},
incrementEnd() {
bus.$emit("incrementEnd");
},
decrementEnd() {
bus.$emit("decrementEnd");
}
}
});
I think it's better to calculate height of container div of those children divs,
You can get height of the element using
element.getBoundingClientRect().height
To access element you can assign a ref to that div and accessing that div like
this.$refs.containerDiv.getBoundingClientRect().height
Afterwards, you can just compare between older value and new value to get how much it decreased / increased.

Is it possible to focus on an 'input' tag that is a child of an element rendered by 'v-if'?

Say I have the following vue.js template code:
<div v-if="x===0">
<input ref="text">{{text}}</input>
</div>
Where x is some data loaded asynchronously.
How can I focus on that 'input' element after the 'if' statement has been evaluated?
Would this approach work? Here I'm deferring this.$refs.textInput.focus() to be executed after the next DOM update cycle (thanks #Bert for reminding) so that Vue could re-render the component (i.e. it adds the input and sets the ref) and we would have the ref to call focus() function.
new Vue({
el: "#app",
data: {
x: 1,
text: 'Hey'
},
methods: {
toggle: function(todo){
todo.done = !todo.done
}
},
mounted() {
setInterval(() => {
this.x = 1 - this.x;
}, 1000);
},
watch: {
x: {
handler: function(value) {
this.$nextTick(() => {
this.$refs.textInput && this.$refs.textInput.focus();
});
}
}
}
})
body {
padding: 20px;
}
#app {
background: powderblue;
padding: 20px;
}
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<div id="app">
<div v-if="x===0">
<input ref="textInput" v-model="text">
</div>
</div>
Would autofocus work?
<div v-if="x===0">
<input ref="text" autofocus>{{text}}</input>
</div>

Categories