Blink or flash text on update of data from websocket - vue.js

Using vuejs with single file components and vuex. Data is streaming in over a websocket connection, and I'm using Vuex to manage state and bind the data to display in various components. That all works great.
I just use mapState in the component file, and then display that value in the html.
For some of the data, I'd like to apply some transition whenever a value changes. i.e.
- flash/blink the value
- flash/blink the background color
I'm displaying numerical values, and thought might look nice to briefly flash green when the number changes up and red when the number changes down. Still debating if I want to flash the actual text, or the div containing the text. Also debating if I want to revert color back to black after the brief color flash.
Is there a way to easily to do this in vuejs? I understand how to use computed values, but how can I fire some color change animation when the value updates from vuex.

You have multiple options, depending on what you eventually decide the effect to be.
Here is the simplest solution. Use a watcher on the computed getter and a conditional class to style your values.
data: {
myValue: {
prev: 150,
latest: 100
}
},
computed: {
valueInStore() {
return this.$store.getters.value
}
},
watch: {
valueInStore(newValue, oldValue) {
this.myValue.prev = oldValue
this.myValue.latest = newValue
}
}
To display the correct css class you then
<div :class="getColorClass(myValye)">{{ myValue.latest }}</div>
...
methods: {
// method instead of computed since you were talking about multiple values
getColorClass(foo) {
return this.myValue.prev < this.myValue.latest ? 'green' : 'red'
},
}
Depending on your needs you might need do adjust for the prev == latest case.
Now you can use any css styles or animations you want in the .green and .red classes.

Related

My component does not react to change Vue with Vuex

I have a state in the Vuex store that reacts to the change in the button and everything changes its dependent value, the problem is that I am using it in a component and I call it in the data()
data() {
return {
currency: this.$store.state.currency,
}
}
When mounting the component, everything is displayed fine, because it takes the value from storage, but when I update that value, the change is not reflected in the component.
I would like to know how I could make the variable render again with the new value of the storage, every time it changes.
I tried using the currency variable where I save the data, it did not work and I also tried to place the variable directly where it is used, but it did not work either
computed: {
currency() {
return this.$store.state.currency;
},
},
Do it like that.

Vue v-model issue when using a computed setter

I want to create an input field that the user can fill out. The catch is that I don't want them to fill this field in with special characters. Currently, I have this html setup:
<input class="rc-input-editing" id="bioInput" type="text" v-model="wrappedBioName">
And this Vue.js setup (As you can see, I'm trying to approach this problem using a computed setter) :
data () {
return {
newBioName: '',
}
},
computed: {
wrappedBioName: {
get () {
alert('getting new name!')
return this.newBioName
},
set: function (newValue) {
const restrictedChars = new RegExp('[.*\\W.*]')
if (!restrictedChars.test(newValue)) {
this.newBioName = newValue
}
}
}
Currently, my issue is that the client is able to continue filling out the text input field, even when this.newBioName isn't updating. In other words, they are able to enter special characters into the input field, even though the this.newBioName isn't being updated with these special characters.
This behavior is different than what I'm expecting, given my current understanding of v-model. Based on what I've read until now, v-model binds the input element to some vue instance data, and that vue instance data to the input element (two way binding). Therefore, I'm expecting that the text in the text input field will directly match the value of this.newBioName.
Clearly, I'm missing something, and would appreciate a second pair of eyes!
Vue.js two way binding system doesn't work as you expected. Each binding process works one way each time. So, the thing you should do is not to let the input text change.
Try keypress event instead of computed property like this:
<input class="rc-input-editing" id="bioInput" type="text" v-model="newBioName" #keypress="nameKeyPressAction">
data() {
return {
newBioName: ""
};
},
methods: {
nameKeyPressAction(event) {
const restrictedChars = new RegExp("[.*\\W.*]");
const newValue = this.newBioName + event.key;
if (!restrictedChars.test(newValue))
this.newBioName = newValue;
return event.preventDefault();
}
}
Edit:
When you set a data property or a computed property as v-model of an input, vue associates them and yet, if user updates dom object via the input, property's setter is triggered and the process ends here. On the other hand, when you change the value of the property on javascript side, vue updates the dom object and this process also ends here.
In your sample code, it seems like you expect that the computed property's getter to set the dom again but it can't. The property is already updated via dom change and it can't also update it. Othervise, there might occur infinite loop.

How to prevent props from reseting when child component updates?

avatarid and relationlist are passed from parent, when an image is uploaded, avatarid changed, but avatarid will be reseted to original if relationlist is changed (add or remove item from RelationTable component).
I think it is that theRelationTable rerendering causes the parent to reload. How can I stop such reseting when child component updates. Thanks.
<template>
<el-upload
class="avatar-uploader"
action
:http-request="uploadAvatar"
accept="image/jpeg,image/jpg,image/png"
:after-upload="uploadAvatarSucc"
>
<RelationTable ref="relationTable" :relationlist="relationlist" #delete="removeRelation" />
</template>
export default {
name: 'relation-component',
props: {
avatarid: {
type: String,
default: ''
},
relationlist: {
type: Array,
default: null
}
},
methods: {
uploadAvatarSucc(res) {
this.avatarid = res.imageId
},
removeRelation(index) {
if (this.relationlist.length > 0) {
this.relationlist.splice(index, 1)
}
}
}
}
You cant stop it - in Vue props are One-Way Data Flow only. If you open the browser Dev Tools you should see error message from Vue telling you exactly this.
Changing relationlist sort of works because you are updating the array in-place (modifying existing instance instead of replacing it ....which is not possible with numbers etc.) but if you try something else (for example creating new array with filter), it will stop working too.
Only correct way around it is to emit event and let the parent component (owner of the data) to do the modifications (google "props down events up"). This of course becomes harder and harder as the depth of the component tree and the distance between data owner and child component increases. And that's one of the reasons why global state management tools like Vuex exist....
Another way is to pass the data by single prop of type Object. As long as you only modify object's properties (not replacing the object itself), it will work. But this not recommended and considered anti-pattern...

Dynamically changing chart types with Vue Chart.js

I'm trying to use Vue Chart.js to implement a chart selector to visualise various data.
Structure of application:
ChartPicker.vue (allow user to pick chart, create data, use dynamic component key to re-render component)
ChartWrapper.vue (receives props and passes them on, creates mixin for dynamic chart type)
ChartRender.vue (simply renders chart)
In the chart render component you usually you need to do 'extends: Bar', 'extends: Line' etc, therefore, requiring a ChartRender component type for each chart type. I found a neat solution that passes in the chart type to the chart mixins, then the final chart render makes no reference to chart type (not quite clear how this works even after looking at vue-chart.js code). This is the example I based my code on (it has no chart type selection):
https://codesandbox.io/s/vue-template-original-1czfi
So, I tried to extend functionality of that example to add a chart selector. It's working to an extent on chart type change: data changes, components re-render but the chart type doesn't change (even though it's being passed to the mixin dynamically)
I have a running example here:
https://codesandbox.io/s/vue-chart-issue-v2-twg3o
I've spent nearly a week trying to figure this out with no joy. I could create a workaround to use a separate ChartRender component for each chart type (e.g. ChartRenderBar, ChartRenderLine etc) but it moves away from DRY, so would rather not.
If anybody could help, I'd be VERY appreciative,
It is possible to dynamically update your chart type with vue-chartjs. The way I did it is by accessing the options in the chart itself and replacing it with the prop I get in which says which chart type it should become and then do an update on the chart. It is not the most elegant solution but it works.
<script>
import { Line, mixins } from 'vue-chartjs';
const { reactiveProp } = mixins;
export default {
extends: Line,
name: "LineChart",
mixins: [reactiveProp],
props: {
options: { type: Object },
chartType: { type: String }
},
mounted () {
this.renderChart(this.chartData, this.options);
},
watch: {
options: {
deep: true,
handler () {
this.$data._chart.options = this.options;
this.updateChart();
}
},
chartType (newVal) {
this.$data._chart.config.type = newVal;
this.updateChart()
}
},
methods: {
updateChart () {
this.$data._chart.update();
},
}
}
</script>
in Vuejs, it is not possible to change mixins after the component being created, and because of this I've used a wrapper component in my other solution, so I can pass a mixin dynamically before the component being created, but unfortunately, there is no way to have some kind of reactive mixin.
my solution for you current situation would be something like that:
https://codesandbox.io/s/vue-template-y5wsw
in the above solution, I have created two components, BarChart and LineChart
to switch between those dynamically, I am using one of the most awesome features in vuejs, Dynamic Component
and of course to avoid duplicating the data source, you can use vuex to share data between multiple components, or you can have the data in the parent page and access your dataset or options like
this.$parent['whatever data property in your parent component']
Hope you found this helpful.

How to use Vue v-model binding in Vuetify components together with computed properties and Vuex?

I'm trying to use v-radio-group in conjunction with computed values from Vuex as described similarly here.
Example codepen of the issue I'm facing is here
Whenever a radio button is clicked, a Vuex mutation is called to save the selected value in the state.
However it can be the case that some validation fails inside the mutation and that therefore the value is not changed in the state as expected.
Regardless of what value ends up in the Vuex state, the radio buttons do not truly reflect the current state.
E.g. in the codepen snippet I'd expect the second option (Option 1) never to show as chosen, as the corresponding state is always 0.
As far as I can see this behavior is not only happening when using v-radio-groups.
It happens with all Vuetify components using v-model and computed getters/setters.
So e.g. Vuetifys v-text-input/v-text-field and v-select also show the same behavior.
To sum it up, my questions are the following:
Why is the second option in my codepen example getting selected even as the corresponding state is different?
How can I achieve the expected result (Having Option 1 never shown as selected, even when it is clicked)?
As far as I know Vuetify keeps its own state in their components like v-radio-group.
To change it you need to send updated props. Then it will react and update its own state.
The trouble is that you are performing validation in a mutation. Which is a bad practice in my opinion.
I will show you how to "block" changing state and update v-radio-group so its own state corresponds to what is actually in your $store.state.radioState.
And I will spend some more time to figure out how to performe it in on mutation ;-)
This is not a perfect solution >> my codepen
Your mutation just updates the state.
// store.js
mutations: {
setRadioState (state, data) {
state.radioState = data;
},
},
Your set method do the validation.
// component
computed: {
chosenOption: {
get () {
return this.$store.state.radioState;
},
set (value) {
if (value !== 1) {
this.$store.commit('setRadioState', value)
} else {
const oldValue = this.$store.state.radioState
this.$store.commit('setRadioState', value)
this.$nextTick(() => {
this.$store.commit('setRadioState', oldValue)
})
}
}
}
}
What happens in the set when it fails validation? You save current state to oldValue, you update state so it corresponds to v-radio-group component. And in the $nextTick you change it right back to oldValue. That way v-radio-group gets updated props and change its state to yours.