How can I mix & match my properties in <p>? - vue.js

I have a <p v-for="index in attrLength" :key="index" class="text-3xl"></p>. The idea is to take an object, in this case const project = projects[index] put it inside the <p> tag, add a . to it, and add the index number after that from the loop, but with the letter "p" injected in front of it, so you'd end up with project.p1, project.p2 and so on, inside the double curly braces like so: <p v-for="index in attrLength" :key="index" class="text-3xl">{{project.p1}}</p>.
How do I do that? I didn't find anything in the docs. Is this even possible?

You can use bracket notation with computed object keys:
<p v-for="index in attrLength" :key="index" class="text-3xl">
{{ project[`p${index}`] }}
</p>
demo

I am assuming attrLength is a number. Hence, as iteration will always start from 1 inside v-for loop. You can access project objects by using projects[index - 1] instead of projects[index].
Live Demo :
new Vue({
el: '#app',
data: {
projects: [{
p1: 'Paragraph 1'
}, {
p2: 'Paragraph 2'
}]
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<p v-for="index in 2" :key="index" class="text-3xl">
{{ projects[index - 1][`p${index}`] }}
</p>
</div>

Related

How to append a static components to a dynamic ones in Vue?

What I'm trying to achieve is this: I have a <v-window> parent component that takes <v-window-item> children. The first child loops thru a Vuex getter that returns an object and depending on its content dynamically visualizes cards. However, I have another static component that is like a summary and contains a logout button that I want to append to the last dynamic <v-window> generated from the store. Here's how I've set up my code so far:
<v-window v-model="reportPage">
<v-window-item v-for="card in getSelectedCard" :key="card.id">
</v-window-item>
</v-window>
Can someone give me some pointers on how to achieve that? Thanks in advance!
I think there are a few ways to achieve such a thing, the one I would use is conditional rendering based on the current index:
new Vue({
el: "#app",
data: {
someList: [ "first", "middle", "last" ]
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<ol>
<li v-for="(item, index) in someList">
{{ item }}
<span v-if="index === someList.length - 1">
- logout button component here
</span>
</li>
</ol>
</div>
Of course the content of my v-if could be a prop of your v-window-item:
<v-window-item v-for="(card, index) in getSelectedCard" :key="card.id" show-logout-button="index === getSelectedCard.length - 1">
Or if you have a slot in your v-window-item:
<v-window-item v-for="(card, index) in getSelectedCard" :key="card.id">
<logout-button v-if="index === getSelectedCard.length - 1" />
</v-window-item>
There are slots that can help you. You simply need to add a <slot></slot> to your child component, then you'll be able to put whatever you like inside your child tag !

passing an input value directly into a function within a loop

So basically I have this;
<ul class="queryView">
<li
v-for="(object, index) in Objects"
>
{{ object.key }}
<input
type="text"
#input="saveValue(value)"
>
</li>
</ul>
...
saveValue(value){
... do somthing with value
},
Since it's in a loop, v-model does not work as they will affect each looped element. i.e If in one input field I put in a word all of the fields will display the same word.
thus I need to get the input value directly into the function.
v-model does work if properly used.
See this JS Fiddle:
https://jsfiddle.net/eywraw8t/167740/
But if you want to use a function to handle the values, it is also fine, but much more verbose.
See this JS Fiddle:
https://jsfiddle.net/eywraw8t/167731/
See the docs: https://v2.vuejs.org/v2/api/#v-on
[…] the method receives the native event as the only argument. If using inline statement, the statement has access to the special $event property: v-on:click="handle('ok', $event)".
The "input" event has a target property, which in your case is your <input> element, on which you can read its current value.
new Vue({
el: '#app',
methods: {
saveValue(event) {
const target = event.target;
const value = target.value;
console.log(value);
},
otherMethod(text1, event) {
console.log(text1);
console.log(event.target.value);
},
},
});
<script src="https://unpkg.com/vue#2"></script>
<div id="app">
<ul>
<li v-for="i in 3">
{{i}}<input #input="saveValue" />
</li>
</ul>
<p>
With inline statement:
<input #input="otherMethod('other', $event)" />
</p>
</div>

[Vue warn]: Duplicate keys detected: x. This may cause an update error

I keep getting an error when I add an item to the array which has duplicate id.
i.e.
active_widgets:Array[4]
0:Object
id:1
name:"Text Blocks"
selected:false
set:false
1:Object
id:3
name:"Bibliographies/References"
selected:false
set:false
2:Object
id:1
name:"Text Blocks"
selected:false
set:false
3:Object
id:2
name:"Free Text"
selected:"Test"
set:false
In my scenario, 'id' element may be duplicate because the user can have the same widget on the page multiple times. I want to know if I can suppress or remove the warning that VueJS keeps throwing in the console.
Same key for different v-for loops causing this warning. You can avoid this using different key for different v-for loops.
<div v-for="(item, i) in items" :key="i"></div>
<div v-for="(item, i) in items2" :key="'A'+ i"></div>
<div v-for="(item, i) in items3" :key="'B' + i"></div>
Here, A and B are just sample characters. You can basically use any character instead of those (just for uniqueness).
An alternative method:
Nesting the v-for elements inside any other element also seems to work.
<div>
<div v-for="(item, index) in items" :key="index"></div>
</div>
<div>
<div v-for="(item, index) in items2" :key="index"></div>
</div>
You need to bind to the key with a unique value. Not doing so will cause problems in your application when a change in data for a component with one key updates that component and the other component with the duplicate key.
You should assign a unique key property to each of the items in the active_widgets array and then bind the key to that property.
Without seeing any of your code, I don't know what your unique use case is. But here are a couple ways you could add a unique key property to the items in your array.
Here's an example doing that in the created method.
created() {
this.active_widgets.forEach((item, index) => this.$set(item, 'key', index));
}
If you need to add a unique key when an item is added to this array, you could maintain a counter and increment it each time an addition is made:
let WidgetCount = 0;
export default {
data() {
return { active_widgets: [] }
},
methods: {
addWidget(id, name) {
this.active_widgets.push({
id,
name,
selected: false,
set: false,
key: WidgetCount++
})
}
}
}
use different key name your problem will be solved.
<div v-for="(item, i) in items" :key="i"></div>
<div v-for="(item, j) in items2" :key="j" :data-index="j"></div>
or
<div v-for="(item, i) in items2" :key="'i+item.id" :data-index="i"></div>
<template v-for="it in items">
<li :key="it.id + '-name'">{{it.name}}</li>
</template>
https://github.com/vuejs/vue/issues/7323
<div v-for="(data, index)" in active_widgets" :key="index">
{{data.id}}
{{data.name}}
{{data.selected}}
{{data.set}}
</div>
I solved this by creating a unique key function to add keys to each of my arrays. Then using it in v-for as the key...
<div
class="postBlob"
v-for="{
key,
user,
post,
dateTime
} in localPostData.slice().reverse()"
:key="key"
>
<strong class="userBlobIndy">{{ user }} </strong>
<h2 class="postBlobIndy">
{{ post }}
<p>{{ dateTime }}</p>
</h2>
</div>
you can use this example
<template>
<div >
<div v-for="categori in blogs" id="blog-body" :key="categori.title" >
<h2 >{{categori.title}}</h2>
<h3>{{categori.contact }}</h3>
</div>
</div>
</template>
<script>
export default {
data(){
return{
blogs:[
{title:'this is title 1',contact : ' this is contact for test javascript'},
{title:'this new title ',contact : ' this is contact for vue'},
{title:'this is new title 2',contact : ' this is contact for vue js'}
]
}
},
}
</script>

Vuejs prevent data to be null

In my component I've data that is succesptible to be invalid
<template>
<p> field </p>
<p> {{ maybeInvalid }} </p>
<p> other field </p>
<template>
var component = {
data: function () {
return {
maybeInvalid: xxx
}
}
I've 3 ideas to fix that :
1 - Use v-if v-else in template
<template>
<p> field </p>
<p v-if="isValid(maybeInvalid)"> {{ maybeInvalid }} </p>
<p v-else> invalid </p>
<p> other field </p>
<template>
...
methods: {
isValid: function (data) {
return data != null;
}
}
2 - Make a computed value
<template>
<p> field </p>
<p> {{ computedIsValid }} </p>
<p> other field </p>
<template>
computed: {
computedIsValid: function() {
return isValid(maybeInvalid) ? maybeInvalid : "invalid";
}
}
3 - build a component
var isValidCompoenent = {
props: ['maybeInvalid'],
template: `
<p v-if="isValid(maybeInvalid)"> {{ maybeInvalid }} </p>
<p v-else> invalid </p>`
methods: {
isValid: function (data) {
return data != null;
}
}
}
and use this component
<template>
<p> field </p>
<is-valid-component :maybeInvalid="maybeInvalid" />
<p> other field </p>
<template>
...
components: {
isValidComponent: isValidComponent
}
I would like to know what's the best (more idiomatic) solution to solve that pattern
Thanks
It's hard to choose which would be more idiomatic; these are all idiomatic solutions to your issue using Vue. In other words, I would not be unhappy to see any of them in a Vue application.
That said, I would steer away from the component solution in this case unless you are likely to use the is-valid component in many places, not just the one component you are talking about here. Additionally, Joe Developer reading your code is not going to know right off the bat exactly what that does; they'll have to go read the code for is-valid.
Using a computed or a v-if are pretty much equivalent in my eyes in this case with one exception. Using the same argument above, Joe Developer reading your template is going to immediately know what happens with v-if but will have to look at the computed to fully understand it. This, however is a very minor thing.

VueJS: How to output a comma separated array?

I know that in VueJS I can loop through an array:
<span v-for="(element, index) in list">{{ element }}</span>
But what if I wanted a list that is comma separated? For example, if list = ["alice", "bob", "chuck"], then the above would output:
<span>alice</span><span>bob</span><span>chuck</span>
What I want, though, is:
<span>alice</span>, <span>bob</span>, <span>chuck</span>
Is this possible?
If all you care about is comma separation, use Javascript's built-in join method:
{{ list.join(', ') }}
For arrays of objects, you could do something like this:
{{ list.map(entry => entry.title).join(', ') }}
You can use conditional rendering to hide last , like following:
var demo = new Vue({
el: '#demo',
data: function() {
return {
lists: ['Vue', 'Angular', 'React']
};
}
})
<script src="https://vuejs.org/js/vue.min.js"></script>
<div id="demo">
<span v-for="(list, index) in lists">
<span>{{list}}</span><span v-if="index+1 < lists.length">, </span>
</span>
</div>
You could do it using a v-if attribute with a condition over the first argument, avoiding the usage of .length:
var app = new Vue({
el: '#app',
data: {
list: ['john', 'fred', 'harry']
}
})
<script src="https://vuejs.org/js/vue.min.js"></script><div id="app">
<span v-for="(element, index) in list">
<span v-if="index != 0">, </span><span>{{ element }}</span>
</span>
</div>
What I ended up doing instead was:
<span v-for="element in list" class="item">
<span>{{ element }}</span>
</span>
And in CSS:
.item + .item:before {
content: ", ";
}
Solution using "template"
<template v-for="(element, index) in list">
<span>{{element}}</span><template v-if="index + 1 < list.length">, </template>
</template>
If you wanted to do it the JS way, you can just do a computed property; you could even continue the span method.
computed {
listCommaSeparated () { return this.list.join(', '); },
listCommaSpans () { return '<span>' + this.list.join('</span><span>') + '</span>'; },
},
This would definitely be the preferred way from a rendering performance standpoint.
Just adding another alternative which I prefer to use:
<span v-for="(item, index) in list">
{{ item }}{{ (index+1 < list.length) ? ', ' : '' }}
</span>
My component:
<template>
<ul v-if="model.length">
<li v-for="item in model">{{item}}</li>
</ul>
</template>
<style scoped>
ul {
list-style: none;
}
li {
display: inline-block;
}
li:not(:last-child)::after {
content: ", ";
}
</style>
<script>
export default {
props: ['model']
};
</script>
Its possible sample
<span v-for="(item,i) in items">
{{(item !='' && i !=0) ? ',' : ''}} {{item.title}}
</span>
If you do want control over how the dom looks (e.g. actually want to achieve the dom structure that you asked about), you can create a functional component like so:
<script>
// RenderList.vue
export default {
functional: true,
render(createElement, context) {
// Read the list of entries by accessing the prop "list"
const listEntries = context.props.list;
// Return a custom list of elements for each list entry.
// Only return a `,` if it's not the last entry.
return listEntries.map((listElement, idx) => {
return [
createElement("span", listElement),
idx < listEntries.length - 1 ? ", " : "",
];
});
},
};
</script>
You would use this component like so:
<template>
<RenderList :list="['alice', 'bob', 'chuck']" />
</template>
May I suggest using i > 0 as check?
I've wrapped the separator so that {{ ' ' }} can be used for just having a space and avoiding span treated as being empty.
<span
v-for="(item, i) in list"
:key="i">
<span v-if="i>0">{{ ', ' }}</span>
<span class="text-nowrap">{{ item }}</span>
</span>
or
<span
v-for="(item, i) in list"
:key="i">
<!-- if not wrapped in a span will leave a space before the comma -->
<span>{{ (i > 0 ? ', ' : '') }}</span>
<span class="text-nowrap">{{ item }}</span>
</span>
There are different views where using :after or :before to have a comma is not a good idea with respect to browser not handling it as a string when the window is resized.
If we try to use conditional operator in each iteration to check whether it is the first or the last element, it would be an overhead if we have large number of items. Also the conditions will be re-evaluated whenever we have change detection.
To overcome these 2 things, we can enclose the comma in a separate element and hide it using CSS :last-child selector as
<template v-for="item in list">
<span class="item">
{{item}}
<span class="comma">
,
</span>
</span>
</template>
and in CSS
.item:last-child .comma {
display: none;
}