Any easier way to access nested object properties in Vue template? - vue.js

It's convenient to group data into nested object properties. By doing this, we don't have to collect properties from the data field into an entity for later use. As in the following example,
var demo = new Vue({
el: '#demo',
data: {
level1: {
level2: {
level3_1: 'Hello',
level3_2: 'world'
}
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.4.2/vue.min.js"></script>
<div id="demo">
<div class="person">
<h3>{{ level1.level2.level3_1 }}</h3>
<p>{{ level1.level2.level3_2 }}</p>
</div>
</div>
However, it's really overkill having to type the "level1.level2" prefix in order to get to the level3_x field. It'll be very cumbersome if there're loads of level3 fields.
I wonder if there is any way that I can save the work for typing level1.level2 over and over again. Does the template have any syntax so that some section is under the scope of "level1.level2"? Does Vue provide any support so that in this case the prefix "level1.level2" is assumed?

There are a couple of options.
1. Use v-for
Everything inside the v-for block is scoped to the level that you're iterating over. Do it like this:
var demo = new Vue({
el: '#demo',
data: {
level1: {
level2: {
level3_1: 'Hello',
level3_2: 'world'
}
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.4.2/vue.min.js"></script>
<div id="demo">
<div class="person">
<template v-for="(l2prop, l2propName) in level1">
<h3>{{ l2prop.level3_1 }}</h3>
<p>{{ l2prop.level3_2 }}</p>
</template>
</div>
</div>
2. Use a component
Components get a subset of their parent's data, so they're automatically scoped. Do it like this:
Vue.component( "person", {
props: ['data'],
template: '<div class="person"><h3>{{ data.level3_1 }}</h3><p>{{ data.level3_2 }}</p></div>'
});
var demo = new Vue({
el: '#demo',
data: {
level1: {
level2: {
level3_1: 'Hello',
level3_2: 'world'
}
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.4.2/vue.min.js"></script>
<div id="demo">
<person v-bind:data="level1.level2"></person>
</div>

The example of #jason-smith is almost right. v-for is used for arrays or lists. To make it work is necessary to put your object in list.
Following his example the better approach would be
var demo = new Vue({
el: '#demo',
data: {
level1: {
level2: {
level3_1: 'Level 3_1',
level3_2: 'Level 3_2'
}
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="demo">
<div class="person">
<template v-for="level2Obj in [level1.level2]">
<h3>{{ level2Obj.level3_1 }}</h3>
<p>{{ level2Obj.level3_2 }}</p>
</template>
</div>
</div>

There are serval ways:
Use a method that gets the same level
methods:{
getLvl3: function(nr){
return this["level"+nr]["level"+nr]["level3_"+nr];
}
{{ getLvl3(1) }}
Iterate over with v-for v-for docu
example:
<div id="demo">
<div class="person">
<template v-for="(lvl2, key) in level1">
<template v-for="(lvl3, key) in lvl2">
<h3 v-if="key === 'level3_1'>{{ lvl3 }}</h3>
<p v-if="key === 'level3_2'">{{ lvl3 }}</p>
</template>
</template>
</div>
</div>
bind to variable that is defined outside of vue:
var nested = { level1: { level2: { level3_1: 'Hello', level3_2: 'world' }}
and inside of vue component or instance:
data:{
level2: nested.level1.level2,
}
<div id="demo">
<div class="person">
<h3>{{ level2.level3_1 }}</h3>
<p>{{ level2.level3_2 }}</p>
</div>
</div>

Related

Vue computed function to match elements from 2 different arrays

Currently, I'm working with Vue v2.x.x. I have an array:
sectionTitles = ['Technology', 'Data', 'Poverty and Research', ...]
and I have jobsData that looks like this:
[{'title': 'Software Engineer', mainTag: 'Data', ...}...]
I want to display <li> in an <ul> when the sectionTitle matches the job.mainTag.
I was reading in the Vue docs that you shouldn't combine v-if with v-for, so I created a computed method to be able to filter the jobs. Here is what I did so far:
window.onload = function () {
var app = new Vue({
delimiters: ['${', '}'],
el: '#app',
data: {
jobs: jobsData,
sectionTitles: ['Data','Poverty Research Unit', 'Technology']
},
computed: {
matchingTitles: function (sectionTitle) {
return this.jobs.filter(function (job, sectionTitle) {
job.mainTag === sectionTitle;
})
}
}
})
}
<div id="app">
<template v-for="title in sectionTitles">
<h4 class="h3">{{ title }}</h4>
<ul class="list-none p-0 color-mid-background" id="jobs-list">
<li class="py-1 px-2" v-for="job in matchingTitles(title)">
<a :href="`${job.url}`">
${job.title}
</a>
</li>
</ul>
</template>
</div>
So basically I want to only display <li> when the sectionTitle (for example Data) matches the job.mainTag. How can I go about achieving this in Vue?
Change your computed method to just a method. Then change your filter to return a value. Also for displaying in Vue you want to use {{....}} not ${...}
new Vue({
el: '#app',
data: {
jobs: [{'title': 'Software Engineer', mainTag: 'Data'}],
sectionTitles: ['Data','Poverty Research Unit', 'Technology']
},
methods: {
matchingTitles: function (sectionTitle) {
return this.jobs.filter ((job)=>{
return job.mainTag === sectionTitle;
})
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<template v-for="title in sectionTitles">
<h4 class="h3">{{ title }}</h4>
<ul class="list-none p-0 color-mid-background" id="jobs-list">
<li class="py-1 px-2" v-for="job in matchingTitles(title)">
<a :href="job.url">
{{job.title}}
</a>
</li>
</ul>
</template>
</div>
#depperm's answer works well (+1), but I'll offer a more render-efficient alternative. Computed properties are cached, so you could avoid the work of matchingTitles() on re-render. In addition, it might be easier to comprehend the template alone without having to jump to the implementation of matchingTitles().
I recommend computing the entire list to be iterated, mapping sectionTitles to the appropriate iterator object:
computed: {
items() {
return this.sectionTitles.map(title => ({
title,
jobs: this.jobs.filter(job => job.mainTag === title)
}))
}
}
Then, you'd update the references in your template to use this new computed prop:
<template v-for="item in 👉items👈">
<h4>{{ item.title }}</h4>
<ul>
<li v-for="job in 👉item.jobs👈">
<a :href="job.url">
{{ job.title }}
</a>
</li>
</ul>
</template>
new Vue({
el: '#app',
data: {
jobs: [{'title': 'Software Engineer', mainTag: 'Data'}],
sectionTitles: ['Data','Poverty Research Unit', 'Technology']
},
computed: {
items() {
return this.sectionTitles.map(title => ({
title,
jobs: this.jobs.filter(job => job.mainTag === title)
}))
}
}
})
<script src="https://unpkg.com/vue#2.6.11/dist/vue.min.js"></script>
<div id="app">
<template v-for="item in items">
<h4 class="h3">{{ item.title }}</h4>
<ul class="list-none p-0 color-mid-background" id="jobs-list">
<li class="py-1 px-2" v-for="job in item.jobs">
<a :href="job.url">
{{ job.title }}
</a>
</li>
</ul>
</template>
</div>

How can I access text inside an input within a template?

My objective is to get text from an input that's in a template. Not sure how to go about retrieving this. I'm using Vue; Note must be available in Vue.js, no external sources
The Template:
<template id="addmodal">
<div class="modal-mask">
<div class="modal-wrapper">
<div class="modal-container">
<div class="modal-header">
<slot name="header">
Enter Course Information:
</slot>
</div>
<div class="modal-body">
<slot name="body">
Course Name
<input type="text" ref="coursename" placeholder="Numbers Don't Lie 101">
Course Grade
<input type="text" ref ="coursemark" placeholder="100">
</slot>
</div>
<div class="modal-footer">
<slot name="footer">
<button class="modal-default-button" #click="confirmCourse">
Submit New Course
</button>
<button class="modal-cancel-button" #click="cancelCourse">
Cancel
</button>
</slot>
</div>
</div>
</div>
</div>
</template>
I need to access coursename and coursemark. This can be done fairly easily when not inside a template. As it is right now the code executes stating .value is undefined.
var app = new Vue({
el: "#app",
data: {
courses: [],
confirmModal: false,
confirmAdd: false,
selectedCourse: null
},
methods: {
addCourse2: function addCourse2() {
this.confirmAdd = false;
var course = this.$refs.coursename.value;
var mark = this.$refs.coursemark.value;
if (course) {
this.courses.push(new Course(course, mark));
this.$refs.newcourse.value = "";
this.$refs.newmark.value = "";
}
}
}
});
EDIT:
Forgot to add the component section
Vue.component("add-modal", {
template: "#addmodal",
props: ["open", "course", "mark"],
methods: {
confirmCourse: function confirmCourse() {
alert(this.$refs.coursename.value);
this.$emit("confirm");// GET
},
cancelCourse: function cancelCourse() {
this.$emit("cancel");
}
}
});
Forgive me in advance, I feel this is something rather easy I'm missing as a beginner
use v-model. or if it is in other component. you can use $emit

Conditional a href in Vuejs

I am rendering a list of store titles in VueJS, some of them have a url property, some of them don't. If the title has a url, I want to add a a href property:
<div v-for="(store, index) in stores">
<span v-if="store.link"><a :href="store.link" target="_blank">{{ store.title }}</a></span>
<span v-else="store.link">{{ store.title }}</span>
</div>
This works, but the code looks duplicated. Is there anyway to simplify the code further?
you can use component tag:
var app = new Vue({
el: '#app',
data () {
return {
stores: [
{title:'product1',link:'/products/222'},
{title:'product2'},
{title:'product3',link:'/products/333'},
{title:'product4'}
]
}
}
})
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<div id="app">
<div v-for="(store, index) in stores">
<component :is="store.link?'a':'span'" :href="store.link || ''" target="_blank">{{store.title}}
</component>
</div>
</div>
I'd remove the first span element, as it's not necessary. Also, the v-else does not need the conditional statement (it's not v-else-if):
new Vue({
el: '#app',
data: {
stores: [
{ link: 'foo', title: 'foo-text' },
{ title: 'bar-text' }
]
}
})
<script src="https://unpkg.com/vue"></script>
<div id="app">
<div v-for="(store, index) in stores" :key="index">
<a v-if="store.link" :href="store.link" target="_blank">{{ store.title }}</a>
<span v-else>{{ store.title }}</span>
</div>
</div>
You can use dynamic arguments in vue3
https://v3.vuejs.org/guide/template-syntax.html#dynamic-arguments
<a v-bind:[attributeName]="url"> ... </a>
or binding an object of attributes
<div v-bind="{ id: someProp, 'other-attr': otherProp }"></div>

Why can't this component access its props data?

When the following component was inline, it was able to access rowData but as a referenced template it can't:
HTML:
<div id="app">
<div>
<row-component v-for="(row, index) in rows" :row-data="row" v-on:delete-row="deleteThisRow(index)"></row-component>
</div>
</div>
<script id="theRow" type="x-template">
row component: {{rowData}}
<button #click="$emit('delete-row')">Delete3</button>
</script>
JavaScript:
Vue.component('row-component', {
props: ["rowData"],
template: '#theRow'
})
new Vue({
el: '#app',
data: {
rows: ["line1", "line2", "line3", "line4", "line5"]
},
methods: {
deleteThisRow: function(index) {
this.rows.splice(index, 1);
}
}
})
How can I get the component to recognize rowData?
https://jsfiddle.net/edwardtanguay/k1tx3z1n/
Components should have only one root element
<div id="app">
<div>
<row-component v-for="(row, index) in rows" :row-data="row" v-on:delete-row="deleteThisRow(index)"></row-component>
</div>
</div>
<script id="theRow" type="x-template">
<div>
row component: {{rowData}}
<button #click="$emit('delete-row')">Delete3</button>
</div>
</script>
See the working fiddle

When one of the data attributes of Vue will change, Vue will re-execute all instruction operations

I just want to change the value of msg,but all the instruction(v-for,v-text,v-bind) of the Vue will re-execute . How to solve this problem?
<div class="container">
<div id="app">
<div v-for="item in list">
<div> {{item.name}} </div>
<div>{{Math.random()}}</div>
</div>
<div>{{Math.random()}}</div>
<div>{{msg}}</div>
<input type="text" v-model='msg'>
</div>
</div>
<script>
var app = new Vue({
el: "#app",
data: {
list: [{ name: "I am Tom" }, { name: "I am Mary" }],
msg: "hello"
}
})
</script>
Because you embedded Math.random() in the template for the VUe, every time the DOM for the Vue needs to be updated (as in when you change msg), a new value will be calculated. One way you might solve this is to initialize the random value when the Vue is created.
var app = new Vue({
el: "#app",
data: {
list: [{ name: "I am Tom" }, { name: "I am Mary" }],
msg: "hello",
random: Math.random()
}
})
And then use the initialized value in your template.
<div id="app">
<div v-for="item in list">
<div> {{item.name}} </div>
<div>{{random}}</div>
</div>
<div>{{random}}</div>
<div>{{msg}}</div>
<input type="text" v-model='msg'>
</div>
Another way you might solve the issue is to isolate the changes to msg in their own scope (a component).
Vue.component("message", {
props:["msg"],
template:`
<div>
<div>{{internalMessage}}</div>
<input type="text" v-model='internalMessage'>
</div>
`,
data(){
return {
internalMessage: this.msg
}
}
})
var app2 = new Vue({
el: "#app2",
data: {
list: [{ name: "I am Tom" }, { name: "I am Mary" }],
msg: "hello",
}
})
And the template:
<div id="app2">
<div v-for="item in list">
<div> {{item.name}} </div>
<div>{{Math.random()}}</div>
</div>
<div>{{Math.random()}}</div>
<message :msg="msg"></message>
</div>
Here is an example of both approaches.