I wanted to make a component which gets text-box when mouse is over the image.
Below is HTML template.
<section class="item-container" v-for="(item, index) in items">
<div class="image-box" #mouseenter="changeStatus(index)">
<img class="image" src="item.link" alt>
</div>
<div class="text-box" #mouseleave="changeStatus(index)" v-if="show[index]">
<h4>{{ item.name }}</h4>
<p>{{ item.content }}</p>
</div>
</section>
And below is app.js
new Vue({
el: '#app',
data: {
show: [false, false, false],
items: [
{
name: 'Author 1',
content: 'Content 1'
},
{
name: 'Author 2',
content: 'Content 2'
},
{
name: 'Author 3',
content: 'Content 3'
}
]
},
methods: {
changeStatus: function(index) {
this.show[index] = !this.show[index];
console.log(this.show);
console.log(this.show[index]); // console gets it as expected
}
}
});
When I execute above codes, I can find that the show property has changed. However, v-if is not updated. Can't we use array[index] for v-if or is there other reason for it?
The problem is not about v-if, it's because Vue cannot detect the changes of an array element directly, this is one of the limitation of JavaScript.
Thus, Vue provides some helper functions for this, like Vue.set
Change this this.show[index] = !this.show[index]
to Vue.set(this.show, index, !this.show[index])
then it should work.
Vue.set is not the only solution, there are several ways to accomplish this, in case you may want to know.
You can use native methods of JavaScript array, Vue will hook on these methods so it can detect the changes.
Here is the example of the usage of .splice
this.show.splice(index, 1, !this.show[index])
Another way is to replace the array entirely. If you are using ES6, you can use the spread operator to clone the array easily:
this.show[index] = !this.show[index]
this.show = [...this.show]
.map will also work because it returns a new array
this.show = this.show.map((el, i) =>
i === index ? !el : el
)
You can use a JS object instead of an array and get the same effect. In other words, replace show: [false, false, false], with show: {0:false, 1:false, 2:false},.
In component in methods you can use:
this.$set(this.show, index, !this.show[index])
Related
How can I access/convert element.varName string in v-for loop to be passed as variable name to get the current value of this boolean.
In the following case:
<div v-for="(element, index) in elements" :key="index" :class="{included : element.varName, 'cur-el':selected==element.shortName}">
<div v-html="element.icon"></div>
{{element.name}}
</div>
el1: false,
el2: false,
selected: undefined,
elements: [
{
name: 'element 1',
icon: `<svg>
<path></path>
<rect></rect>
</svg>`,
shortName: 'el1',
varName: this.el1
},
/...
]
How can my included class be a boolean value instead of the actual string, initially I tried with the shortName used accessing it as follow:
element.shortName
which didn't work, also tried:
[element.shortName]
as well as:
this[element.shortName]
None of which seems to work, so I tried including the actual reference to that variable by adding it in the object varName: this.el1, which also didn't work.
What am I doing wrong?
Since you're referencing a data property in other one you should define the second property as computed property :
data(){
return {
el1: false,
el2: false,
selected: undefined,
}
},
computed:{
elements(){
return [
{
name: 'element 1',
icon: `<svg>
<path></path>
<rect></rect>
</svg>`,
shortName: 'el1',
varName: this.el1
}
]
}
}
The reason why varName: this.el1 is not updated inside data option, is because it's not reactive.
You can read about that in Vue official documentation here.
Back to your question :
Try to assign the whole elements array in mounted() life cycle hook. So, that you can access this.el1.
Demo :
new Vue({
el:"#app",
data: {
el1: false,
el2: true,
elements: []
},
mounted() {
this.elements = [
{
name: 'element 1',
shortName: 'el1',
varName: this.el1
}, {
name: 'element 2',
shortName: 'el2',
varName: this.el2
}
]
}
});
.included {
background-color: yellow
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<div v-for="(element, index) in elements" :key="index" :class="{included : element.varName}">
{{element.name}}
</div>
</div>
I am trying to create a dropdown (v-select/q-select (using quasar)), which allows me to select from an array in my vuex-storage and then eventually save the selected item (content of it) in a variable. Currently I have no problem to access the vuex-storage, but face the problem, that the v-select expects a string and not an object.
My code looks like the following.
// vuex storage:
const state = {
savedsystems:
[
id: "1",
system: {...}
],
[
id: "2",
system: {...}
]
// example of the vuex storage out of my viewdevtools
systemsconstant: Object
savedsystems:Array[2]
0:Object
id:"first"
system:Object
7a73d702-fc28-4d15-a54c-2bb950f7a51c:Object
name:"3"
status:"defined"
88519419-8a81-48f1-a5e6-5da77291b848:Object
name:"5"
status:"not defined"
1:Object
id:"second"
system:Object
7a73d702-fc28-4d15-a54c-2bb950f7a51c:Object
name:"3"
status:"not defined"
88519419-8a81-48f1-a5e6-5da77291b848:Object
name:"9"
status:"defined"
}
// dropdown:
<q-select
outlined
dense
emit-value
:value="currentsystem"
:options="savedsystems"
label="selectsystem" />
// computed to get systems from vuex:
computed: {
savedsystems() {
return this.$store.getters['systemsconstant/getsavedsystems']
}
},
I used the following example https://codepen.io/sagalbot/pen/aJQJyp as inspiration and tried a couple of different setups stringifying resulting in nothing really.
If one would try to apply my case to a similar problem (v-select displays object Object), the mentioned formatlabel would be an object instead of a string.
Question:
How can I modify the (with a getter) imported array of objects "savedsystems", so it can be used both as label to select it and furthermore then to connect it properly to the values, so I can save the selected as a variable.
Or can I change something in my v-select, e.g. varying what comes behind :options/options?
I'd appreciate any help!
You should use the property option-label
<div id="q-app">
<div class="q-pa-md" style="max-width: 300px">
<div class="q-gutter-md">
<q-badge color="secondary" multi-line>
Model: "{{ model }}"
</q-badge>
<q-select filled v-model="model" :options="options" label="Standard" option-label="description"></q-select>
{{ model }}
</div>
</div>
</div>
JS:
new Vue({
el: '#q-app',
data () {
return {
model: null,
options: [
{
label: 'Google',
value: 'Google',
description: 'Search engine',
category: '1'
},
{
label: 'Facebook',
value: 'Facebook',
description: 'Social media',
category: '1'
},
{
label: 'Twitter',
value: 'Twitter',
description: 'Quick updates',
category: '2'
},
]
}
}
})
https://codepen.io/reijnemans/pen/bGpqJYx?editors=1010
I send an array of objects which each have a .html property that has HTML text in it, e.g. <h1>...</h1> or <h2>...</h2>
I want to have the HTML from each item display one after another in the DOM, like this:
<h1>...</h1>
<h2>...</h2>
<h2>...</h2>
<h1>...</h1>
<h2>...</h2>
However, all of these attempts do not work:
<div v-for="item in outlineItems" v-html="item.html"></div>
displays HTML wrapped in divs: <div><h1>...</h1></div> and <div><h2>...</h2></div>
<template v-for="item in outlineItems" v-html="item.html"></template>
displays nothing
<template v-for="item in outlineItems">{{item.html}}</template>
displays the literal HTML instead of rendering it
<template v-for="item in items"><template v-html="item.html"></template></template>
displays nothing
How can I simply display the contents of the .html property of each item so that the HTML in it renders, without any wrapping elements on it?
You could do it using a single wrapper element for the whole lot by concatenating all the HTML in a computed property:
new Vue({
el: '#app',
data () {
return {
outlineItems: [
{ html: '<h1>Heading 1</h1>' },
{ html: '<h2>Heading 2</h2>' },
{ html: '<h3>Heading 3</h3>' }
]
}
},
computed: {
outlineHtml () {
return this.outlineItems.map(item => item.html).join('')
}
}
})
<script src="https://unpkg.com/vue#2.6.11/dist/vue.js"></script>
<div id="app">
<div v-html="outlineHtml"></div>
</div>
Behind the scenes v-html sets the innerHTML of its corresponding DOM node. A <template> tag doesn't create a DOM node so the innerHTML can't be set anywhere.
I would add that v-html is considered an 'escape hatch'. Where possible you should avoid using it and let Vue create the HTML itself. Generally the approach would be to use a suitable data structure to hold the data (rather than a blob of markup) and then render that data structure within the template.
One possible solution is to create multiple unique components. You can even pass in props, and there are no wrappers
Vue.component('greeting', {
template: '<h1>Welcome to coligo!</h1>'
});
Vue.component('titles', {
template: '<h1>title 1</h1>'
});
Vue.component('title2', {
template: '<h2>Welcome to coligo!</h2>'
});
Vue.component('title3', {
template: '<h3>{{text}}</h3>',
props: ['text']
});
var vm = new Vue({
el: '#app',
data: {
items: [
{ type: 'greeting' },
{ type: 'titles' },
{ type: 'title2' },
{ type: 'title3', text: 'test' }
]
}
});
<script src="https://unpkg.com/vue#2.6.11/dist/vue.js"></script>
<div id="app">
<component v-for="(item,i) in items" :is="item.type" :text="item.text" :key="i"></component>
</div>
In this fiddle : https://jsfiddle.net/djsuperfive/svctngeb/ I want to reverse the structure array in data when I click the button.
Why are the rendered list and the dump of structure not reactive whereas the console.log reflect that the reverse was effective ?
The code :
HTML
<div id="app">
<draggable v-model="structure">
<div v-for="(item, index) in structure" :style="'background-color:'+item.color">
{{ item.title }}
</div>
</draggable>
<button type="button" #click="reverse()">Reverse structure</button>
<hr>
<strong>dump structure:</strong>
<pre>
{{ structure }}
</pre>
</div>
JS:
new Vue({
el: '#app',
data() {
return {
structure: [{
title: 'Item A',
color: '#ff0000'
},
{
title: 'Item B',
color: '#00ff00'
},
{
title: 'Item C',
color: '#0000ff'
},
],
}
},
methods: {
reverse() {
console.log(this.structure[0].title);
_.reverse(this.structure);
console.log(this.structure[0].title);
}
}
});
thanks
Max!
Try to replace line:
_.reverse(this.structure);
with
this.structure.reverse();
I guess, Underscore.js does not have reverse method, because JavaScript has native one.
Everything should work fine with native JS Array reverse function.
Good luck!
The problem here seems to be that Vue does not detect that your array has changed: https://v2.vuejs.org/v2/guide/reactivity.html#Change-Detection-Caveats
The solution is to either replace this.structure with a new array or to have a reverse function that uses Vue.set().
this.structure = _.clone(_.reverse(this.structure));
I am working on a page using vue-bootstrap that has a b-form-checkbox-group embedded in each row. Those are appearing fine. The problem comes when I try to assign values to the checked prop. When i assign a static number the boxes will be checked like they should be when the page loads but when I try to set it up to receive responses from jquery get calls the boxes are only sometimes filled with no pattern as far as I can see.
<nit-card :title="host">
<b-table
:items="interfaceNames"
:fields="switch_interface_config_col_names">
<template slot="tagged" slot-scope="row">
<b-form-checkbox-group :options="vlans"
:checked="interfaceNames[row.index].taggedVlans"
stacked>
</b-form-checkbox-group>
</template>
<template slot="untagged" slot-scope="row">
<b-form-radio-group :options="vlans"
:checked="interfaceNames[row.index].untaggedVlans"
stacked>
</b-form-radio-group>
</template>
</b-table>
</nit-card>
Above is the html for the table. nit-card is a vue.component that looks like this.
Vue.component('nit-card', {
props: ['title'],
template: `
<div class="card">
<div class="card-header">
<h2 class="card-title">{{ title }}</h2>
</div>
<div class="card-body">
<slot></slot>
</div>
</div>
`
})
And some of the js code with an exmaple of ine of the methods that are called to get the data for the checked prop.
let generic_name = new Vue({
el: '#generic_name',
data: function(){
return{
interfaceNames: [],
vlans: [],
switch_interface_config_col_names: [
{key: "interfaceName", sortable: true},
{key: "description", sortable: false},
{key: "tagged", sortable: false},
{key: "untagged", sortable: false},
],
submit_params: [
["Switch", "text", "Hostname or IP", true],
["Interface", "text", "Interface Name", true],
],
host: "",
}
},
methods: {
getTagged: function(){
let that = this
for(let i = 0; i < that.interfaceNames.length; i++){
let url_encoding_re = /\//g;
let interfaceName = that.interfaceNames[i].interfaceName.replace(url_encoding_re, '%5c')
$.get(api_url + "/switches/" + that.host + "/interfaces/" + interfaceName + "/vlans/tagged", function(response) {
console.log(response)
that.interfaceNames[i].taggedVlans = response.vlans
})
}
},
}
The way that I imagine it flowing is that the interfaceNames array stores the data and then it is accessed by the html using the row.index value. What is actually happening is that maybe half of the checkbox forms have the checked values in their checked prop even though when I look at the root vue the values are present in interfaceNames[x].taggedVlans.
What do i need to do to have the checked prop filled consistently for these check-box-groups?
EDIT: By toggling the table with a v-if manually (Ex a button after the table renders) all of the checkboxs will appear correctly. Once I have a reliable way to toggle automatically the table I will post it here.
The issue came from how i was adding the objects to the interfaceNames array. Vue.js cant detect changes made to arrays when you directly set the value through the index. To make the array reactive use Vue.set(this.arrayName, indexValue, new value) (as seen here https://v2.vuejs.org/v2/guide/list.html#Caveats)
For this specific example above Vue.set(that.interfaceNames, i, {key: value, key: value, ...}) worked. Since it was done in a loop for all the values of the array the entire array was made reactive.