I'm trying to change the id of an image after the user clicks on a button. Initially, the id of the element should be B0_h, but after the user clicks on a button, a value in an array should change to true. Initially all the array values are false, but once the value of the element in the array becomes true, the id should change to B0_v.
Using the Vue dev tools, I noticed that the value in the array is changing as expected, however, v-bind can't detect this change. From the v-bind perspective, the value of B[0] is still false. As a result, the id is still B0_h.
Here's my template:
<template>
<div>
<button type="button" id="button0" v-on:click="toggle('B0')"></button>
<img src="../assets/img1.png" alt="A" v-bind:id="[A[0] === true ? 'A0_v' : 'A0_h']" >
<img src="../assets/img2.png" alt="B" v-bind:id="[B[0] === true ? 'B0_v' : 'B0_h']">
</div>
</template>
Here's my script:
<script>
export default {
name: 'Demo',
props: {},
data: function(){
return{
A: [false,false,false,false,false,false,false,false,false],
B: [false,false,false,false,false,false,false,false,false],
playerTurn: true;
}
},
methods:
{
toggle(x)
{
if(x == 'B0' && this.playerTurn)
{
this.B[0] = true;
}
}
}
}
</script>
Any idea what I'm doing wrong in here?
This is due to the handling of changes by Vue in arrays and objects. Vue won't track the change you're making. For this purpose it offers a special method: set. It takes 3 arguments: the array (or the object) that has to be changed, the index, and the value that should be set.
In your example it'll look like this:
Vue.set(this.B, 0, true);
Put this line instead of:
this.B[0] = true;
For more information please check the official documentation. This is a short excerpt:
Vue.set( target, propertyName/index, value ) Arguments:
{Object | Array} target
{string | number} propertyName/index
{any} value
Returns: the set value.
Usage:
Adds a property to a reactive object, ensuring the new property is
also reactive, so triggers view updates. This must be used to add new
properties to reactive objects, as Vue cannot detect normal property
additions (e.g. this.myObject.newProperty = 'hi').
Related
I'm trying to do what I would think is rather simple, but I can't seem to find a built-in way to do it. Basically, I have several divs on a page that are using v-if conditions which are acting as filters for data on the page (think about a table with data that is then filtered by select boxes)
Below is a very simplified example, but basically, I'm wanting to set a variable in my data object once a v-if condition is satisfied. Then, if filters change and make a different v-if condition satisfied it would set the same variable to a different value.
I basically want a value that can be changed based on any filters on my page, as long as I have a way to set that value after any given v-if is satisfied.
I was hoping to be able to simply call a method (with an argument passed) once v-if resolved is possible
var vm = new Vue({
el: "#app",
props: {
},
data: {
showData: 'ABC',
specificData: "here are some specifics",
newValue: ''
}
});
<div id ="app">
<div v-if="showData === 'ABC'">
<!--Here, I want to set newValue to something like 'ROGER' unrelated to ABC-->
ABC
</div>
<div v-if="showData === '123'">
<!--Here, I want to set newValue to something like 'SAM' unrelted to 123-->
123
</div>
</div>
This kind of business logic should not be handled in template, but rather in a computed property. The most basic setup would look like:
data(){
return {
showData: "ABC"
}
},
computed: {
newValue(){
if (this.showData === "ABC") {
return "Some derived value"
}
return ''
}
}
Alternatively, you can use a watcher on showData, and call additional methods when any of the conditions are met.
watch: {
showData(val){
if (val === "ABC") {
this.newValue = "Some derived value"
this.someOtherMethod()
}
// Any other conditions to be checked
// or simply pass the value further to a method where all the checks are done
this.checkValue(val)
}
}
I am using VueJS and trying to use v-model. to connect checkboxes to values in an object:
jobsChecked: {Baker: false, Cook: false, Miner: false} // etc...
and the checkbox element:
<div class="checkbox jobs" v-for="j in jobs" :key="j">
<label>
<input type="checkbox" v-model="filters.jobsChecked.j"
#change="filterJobs(j)"> {{ j }}
</label>
</div>
with filterJobs() firing when I click one of these boxes:
filterJobs(j) {
if (!this.filters.jobs.includes(j)) {
this.filters.jobs.push(j);
this.filters.jobsChecked[j] = true;
} else {
const index = (ele) => ele === j;
const remove = this.filters.jobs.findIndex(index);
this.filters.jobs.splice(remove, 1);
this.filters.jobsChecked[j] = false;
}
console.log("jobs filters: ", this.filters.jobs);
console.log("jobs checked: ", this.filters.jobsChecked);
}
Expected output: The function receives "j" which is read as the string "Baker". The syntax for modifying an Object's property works and checking "Baker" pushes Baker to the filters.jobs array AND uses filters.jobsChecked.j to make filters.jobsChecked.Baker = true
Actual output: The function receives "j" which is read as the string "Baker", pushes "Baker" into the filters.jobs array, but then adds the key/value 'j: true' to filters.jobsChecked.
My confusion comes in here where I am expecting that, since the rest of the function seems to be fully aware the 'j' is not actually the letter j, but instead a passed variable with the value of "Baker", it will know that "filters.jobsChecked.j" = "filters.jobsChecked.Baker" and using object modification syntax, it should change the value to true as I wrote.
I have been reading other posts and websites all night about this and nothing I found made any sense or seemed to englighten me on this situation. As far as I can see, I am writing it exactly as it should be and using [] square notation since it's a variable being passed in.
I also tried writing ['j'] but that also just ADDED the key value "j: false" to the object...
What am I missing here? I have a feeling it's something stupidly obvious...
There is invalid syntax for accessing the string contained in j in the context of an object property.
filters.jobsChecked.j is looking for a property named "j" on the jobsChecked object, but it doesn't exist. Use bracket notation to evaluate j as a variable:
filters.jobsChecked[j]
I have computed property in my data this.coinPairingOptions that needs to render its radio buttons based on some of the other fields in this schema. I have reduced the amount of code in the file to save space.
data: function () {
return {
schema: {
{model: "symbolPair", type: "radios", label: "Pair with", values:
this.coinPairingOptions, required: true}
},
computed: {
coinPairingOptions() {
console.log("computing coinPairingOptions")
let coin = this.model.symbol.toUpperCase();
let options = [];
if (this.model.exchange === 'Coinbase') {
options = this.getCoinbasePairs
} else if (this.model.exchange === 'Binance') {
options = this.getBinancePairs
} else {
}
console.log(options.get(coin));
return options.get(coin);
},
}
In the dev tools I can see the computed property changing to the correct values however it is not changing in the data. Apparently, this is appropriate behavior, but what is a way around this? I have tried putting {{this.coinPairingOptions}} in the html and it errors because it's a computed property with not value initially.
Any help would be appreciated!
You can't use computed property in data, because data evaluates before the computed properties did.
You can use a watcher to achieve the intended result. Have a look at the documentation, you can add the argument immediate to trigger the callback immediately with the current value of the expression.
Computed properties are already accessible in the template by using {{}}. You don't need to put a this in front of the computed.
I am trying to dynamically create/remove a Vue component. I have figured out how to dynamically add the component, but I am having some troubles with allowing the users to remove the specific component.
Consider below two Vue files:
TableControls.vue
<a v-on:click="addColumn">Add Column</a>
<script>
export default {
methods: {
addColumn: function () {
Event.$emit('column-was-added')
}
}
};
</script>
DocumentViewer.vue:
<div v-for="count in columns">
<VueDragResize :id="count">
<a #click="removeColumn(count)">Remove Column</a>
</VueDragResize>
</div>
<script>
import VueDragResize from 'vue-drag-resize';
export default {
components: {
VueDragResize
},
data() {
return {
columns: [1],
}
},
created() {
Event.$on("column-was-added", () => this.addColumn())
},
methods: {
addColumn: function () {
this.columns.push(this.columns.length + 1)
},
removeColumn: function (id) {
this.columns.splice(id, 1)
}
}
};
</script>
As you can see, whenever a user clicks on <a v-on:click="addColumn">Add Column</a>, it will submit an event, and the DocumentViewer.vue file will pick up it, firing the addColumn method. This will ultimately create a new <VueDragResize></VueDragResize> component.
This works great.
The problem is when I want to remove the component again. My removeColumn method simply removes an id from the columns array:
removeColumn: function (id) {
this.columns.splice(id, 1)
}
This results in that a column is in fact removed. However, consider below example. When user clicks on the remove icon for the first column, it will remove the 2nd column instead. (And when there is only one column present, it cannot be removed).
I believe this is due to the fact that I splice() the array, but I cannot see how else I can remove the component dynamically?
I see, Array on Vue does not re render when you modify them.
You need to use the
Vue.set(items, indexOfItem, newValue)
if you want to modify
and use
Vue.delete(target, indexOfObjectToDelete);
If you want to delete an item from an array
You may read the additional info here
https://v2.vuejs.org/v2/api/#Vue-delete
If you want to delete an item from array. Using this will cause the component to rerender.
In this case it will be intuitive to do this
removeColumn: function (id) {
Vue.delete(this.columns, id)
}
Note that id should be the index. Vue.delete ensures the re-render of the component.
EDIT, you must use the index, instead of the count here.
<div v-for="(count, index) in columns">
<VueDragResize :id="index">
<a #click="removeColumn(index)">Remove Column</a>
</VueDragResize>
</div>
I would recommend reshaping your data, each element should be an object with an id and whatever other properties you want. Not simply an id then you would need something like this:
removeColumn(id) {
const elToRemove = this.columns.findIndex(el => el.id === id)
let newArr = [elToRemove, ...this.columns]
this.columns = newArr
}
Also make another computed property for columns like this to make sure they change dynamically (when you add/remove):
computed: {
dynColumns(){ return this.columns}
}
I have same problem, and I found the solution of this problem. It is need to set #key with v-for. This is Built-in Special Attributes.
By default, if you do not set "#key", array index is set to#key. So if array length is 3, #key is 0,1,2. Vue identify eash v-for elements by key. If you remove second value of array, then array index is 0 and 1, because array length is 2. Then Vue understand that #key==2 element removed, So Vue remove 3rd component. So if you remove second value of array, if no #key, third component will be removed.
To avoid this, need to set #key to identify component like this:
let arr = [
{ id: 'a', ...},
{ id: 'b', ...},
{ id: 'c', ...}
];
<div v-for="obj in arr" :key="obj.id">
<someYourComponent>
...
</someYourComponent>
</div>
I'm creating a custom widget in Wakanda.
This widget must change a class in function of a property. For example, if the property shadowDepth=2 then I will set the class as mdl-shadow--2dp, else if shadowDepth=6 I will set the class as mdl-shadow--6dp.
How could I change the class this way?
The most used approach is the following:
Add an enum attribute to the custom Widget.
Consider the existing class at Widget initialisation.
Make an onChange trigger to apply your desired classes.
Let's view each step separately:
1. Add an enum custom attribute
I expect you know how to create a custom widget. Let's add the attribute to the widget definition in widget.js:
var myWidget = widget.create('myWidget', {
// widget attributes
});
for an enum attribute you can define it as it follows:
shadowDepth: widget.property({
type: 'enum',
description: 'Shadow depth for MDL widget',
values: {
'mdl-shadow--1dp': '1',
'mdl-shadow--2dp': '2',
'mdl-shadow--3dp': '3',
'mdl-shadow--4dp': '4',
'mdl-shadow--5dp': '5',
'mdl-shadow--6dp': '6'
},
defaultValue: 'mdl-shadow--2dp',
bindable: false
})
This way you have added your enum attribute which does nothing for the moment.
2. If the widget has already one of the classes, set the property attribute value (initialValue)
In order to retrieve the initial value of the widget, which can be assigned in the DOM, we define the defaultValueCallback function as it follows in the attribute property just after defaultValue:
defaultValueCallback: function() {
var r = /mdl-shadow--\ddp/.exec(this.node.className); // regex are cool
return r[r.length - 1] || 'mdl-shadow--2dp'; // return the latest occurrence of the class or the default value
},
3. the onChange trigger
In the init attribute declaration of the widget add the following code:
init: function() {
// first call to set the attribute
this._changeShadowDepth(this.shadowDepth());
// onChange event
this.shadowDepth.onChange(this._changeShadowDepth);
}
and add the _changeShadowDepth function as it follows:
_changeShadowDepth: function(value) {
var $node = $(this.node); // jQuery is included in the prototype, do not worry
[
'mdl-shadow--1dp', 'mdl-shadow--2dp', 'mdl-shadow--3dp',
'mdl-shadow--4dp', 'mdl-shadow--5dp', 'mdl-shadow--6dp'
].forEach(function(klass) {
$node.removeClass(klass);
});
if (value) {
$node.addClass(value);
}
},
This is the same exact approach used by the official Image widget to change the align (background-align) property.