set unique key in v-for loop - javascript

I'm working with BootstrapVue.
How can I set a unique id each time I'm inputing a new object to my template?
I've just tried to show it in my template (here: {{????}}) to see if its working but only output I get is #
Thanks in advance!
<template>
<div>
<div v-for="idInput in inputs" :key="idInput._id">
<b-form-input class="col-md-6"></b-form-input>#
{{????}}
</div>
<b-button #click="addInput()">Add Input</b-button>
</div>
</template>
<script>
export default {
methods: {
addInput() {
this.inputs.push({});
}
},
data() {
return {
inputs: [{}],
}
}
}
</script>

Use the v-for index parameter, this should give you a unique index for each element.
Try it like this:
<div v-for="(idInput, index) in inputs" :key="index">
<b-form-input class="col-md-6"></b-form-input>#
{{index}}
</div>

You can replace to h2 to anything that you want to give a dynamic Id.
Here is my solution (based on an example with h2 element) :
<template>
<div >
<div v-for="(idInput,index) in inputs" :key="index">
<b-form-input class="col-md-6"></b-form-input>#
<h2 :id="idInput._id">{{idInput.text}}</h2>
</div>
</div>
</template>
<script>
export default {
data() {
return {
inputs: [{"_id":"first_id","text":"Bobby has"},
{"_id":"second_id","text":"a good taste"},
{"_id":"third_id","text":"for frameworks!"}]
}
},
}
</script>
<style>
#first_id {
color:black;
}
#second_id {
color:red;
}
#third_id {
color: yellow;
}
</style>

Related

Custom multi-select and single select issue

i have created a component which works perfect apart from binding the a string rather than an array. The event's fire correctly.
So on the multi-select version if I select and deselect items the multiSelected variable is updated correctly. However when selecting another value within the single select the singleSelected variable isn't being changed, however the same event is firing.
Dummed down the code here so you can just see the logic and issue:
Vue.component('dropdown', {
template: `<div class="dropdown">
<label v-for="item in items" :key="item.value" :class="{selected:selected.indexOf(item.value) > -1}">
{{ item.label }}
<input type="checkbox" :value="item.value" :checked="selected.indexOf(item.value) > -1" #change="selected = $event" />
</label>
</div>`,
props: [ 'value', 'items', 'multiSelect' ],
computed: {
selected: {
get: function() {
if (this.value === undefined) {
return [];
}
if (!Array.isArray(this.value)) {
return [ this.value ];
}
return this.value;
},
set: function($event) {
let current = this.selected;
if (!this.multiSelect) {
current = $event.target.value;
}
if (this.multiSelect && !$event.target.checked) {
const index = current.indexOf($event.target.value);
if (index > -1) {
current.splice(index, 1)
}
}
if (this.multiSelect && $event.target.checked) {
current.push($event.target.value);
}
console.log(current);
this.$emit('value', current);
}
}
}
});
Vue.component('wrapper', {
template: `
<div>
Single
<dropdown :items="items" v-model="singleSelected" :multi-select="false" name="single" />
<br />
Multi
<dropdown :items="items" v-model="multiSelected" :multi-select="true" name="multi" />
<p>Models</p>
<p>singleSelected: {{ singleSelected }}</p>
<p>multiSelected: {{ multiSelected }}</p>
</div>
`,
data() {
return {
items: [{value:'bmw',label:'BMW',count:1},{value:'audi',label:'Audi',count:1},{value:'kia',label:'KIA',count:1}],
multiSelected: ['kia'],
singleSelected: 'kia',
}
}
});
new Vue().$mount('#app');
.dropdown {
border: 1px solid black;
padding: 10px;
display: block;
}
label {
margin: 5px;
}
.selected {
color: red;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<wrapper>
</wrapper>
</div>
v-model on custom component expects that the component will emit input event, not value
Only reason why your component is (sort of) working in multi-select mode is because you are directly mutating the input array passed to you by a value prop (in other words the emitted event is useless). Don't do that. Create a new array instead before emitting
Also, there is one more issue with single-select mode - after deselecting only checked box, you still emitting the value so the model is set to a value even none of the checkboxes is checked
Personally I find your design strange for several reasons:
Using different v-model "type" (array or string) feels unnatural
In single-select mode it behaves as radio so why not use radio
What will happen if you switch multiSelect prop at runtime?
Vue.component('dropdown', {
template: `<div class="dropdown">
<label v-for="item in items" :key="item.value" :class="{selected:selected.indexOf(item.value) > -1}">
{{ item.label }}
<input type="checkbox" :value="item.value" :checked="selected.indexOf(item.value) > -1" #change="selected = $event" />
</label>
</div>`,
props: [ 'value', 'items', 'multiSelect' ],
computed: {
selected: {
get: function() {
if (this.value === undefined) {
return [];
}
if (!Array.isArray(this.value)) {
return [ this.value ];
}
return this.value;
},
set: function($event) {
if(this.multiSelect) {
if (!$event.target.checked) {
this.$emit('input', this.selected.filter(v => v !== $event.target.value))
} else {
this.$emit('input', [...this.selected, $event.target.value])
}
} else {
this.$emit('input', $event.target.checked ? $event.target.value : "")
}
}
}
}
});
Vue.component('wrapper', {
template: `
<div>
Single
<dropdown :items="items" v-model="singleSelected" :multi-select="false" name="single" />
<br />
Multi
<dropdown :items="items" v-model="multiSelected" :multi-select="true" name="multi" />
<p>Models</p>
<p>singleSelected: {{ singleSelected }}</p>
<p>multiSelected: {{ multiSelected }}</p>
</div>
`,
data() {
return {
items: [{value:'bmw',label:'BMW',count:1},{value:'audi',label:'Audi',count:1},{value:'kia',label:'KIA',count:1}],
multiSelected: ['kia'],
singleSelected: 'kia',
}
}
});
new Vue().$mount('#app');
.dropdown {
border: 1px solid black;
padding: 10px;
display: block;
}
label {
margin: 5px;
}
.selected {
color: red;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<wrapper>
</wrapper>
</div>

How to have things data in vuejs to be printed in console

Say I have a data object and in that object, there is a property called anime1. How would I access have anime1 printed on the console as the user types their input in. This entire thing is in a component. I tried creating a function called log that when pressed would console.log the value of anime1 but that didn't work. This is what I've tried out so far.
<template>
<section class="hero">
<div class="parent-1">
<h1 class="title is-1">Compare two animes! :)</h1>
</div>
<div class="columns">
<div class="column">
<b-field class="label" label="Anime 1">
<b-input value="Enter the first anime!" v-model="anime1"></b-input>
</b-field>
</div>
<div class="column">
<b-field class="label" label="Anime 2">
<b-input value="Enter the second anime!" v-model="anime2"></b-input>
</b-field>
</div>
</div>
<div class="button-spacing">
<b-button class="button" type="is-primary" click="#log"
>Compare!</b-button
>
</div>
</section>
</template>
<script>
import Vue from "vue";
import Buefy from "buefy";
import "buefy/dist/buefy.css";
Vue.use(Buefy);
export default {
data() {
return {
anime1: "",
anime2: "",
};
},
methods: {
log(anime1) {
console.log(anime1);
},
},
};
</script>
<style>
.title.is-1 {
text-align: center;
}
.parent-1 {
margin-top: 10%;
}
.columns {
margin-top: 2%;
margin-right: 10%;
margin-left: 10%;
}
.label {
text-align: center;
}
.button {
width: 10%;
}
.button-spacing {
text-align: center;
}
</style>
To call a function as you type on the input field, add #input to it.
<b-input value="Enter the first anime!" v-model="anime1" #input="log()"></b-input>
Then, you can easily access the anime1 inside your log() using this.anime1 so you don't need to put parameters on it.
data() {
return {
anime1: "",
anime2: "",
}
},
methods: {
log(){
console.log(this.anime1)
}
}
Alternatively, as #Sphinx said in the comment, you can watch the anime1, then call a function once it changes.
<b-input value="Enter the first anime!" v-model="anime1"></b-input>
data() {
return {
anime1: "",
anime2: "",
}
},
watch: {
anime1(newVal) {
this.log(newVal);
}
},
methods: {
log(string) {
console.log(string);
},
}
Also, the way you add your click handler on your button won't work. It should be v-on:click, or simply #click. More on event handlers here.
<b-button class="button" type="is-primary" #click="log('comparing...')">Compare!</b-button>

How to conditionally append an element in a scoped Vue Component?

I am trying to create a Component for titles that can be edited when they get double clicked.
The Component takes the h-tag that should be used and the title as props and should produce a normal h-tag, that turns into an input field once double clicked.
This already works if there is only one title on the page, however once there are multiple Components used on one page, it breaks as the Component is not scoped properly. But I can't figure out how.
Here is the code:
<template>
<div class="edit-on-click">
<input
:class="sizeClass"
type="text"
v-if="edit"
v-model="editedTitle"
#blur="finishEdit"
#keyup.enter="finishEdit"
v-focus="true"
/>
<span v-show="!edit" #dblclick.prevent="edit = true"></span>
</div>
</template>
The mounted hook I can't figure out how to scope:
mounted() {
let node = document.createElement(this.size); // Takes h-tag (h1, h2 etc.)
let titleText = document.createTextNode(this.finalTitle); // Takes title
node.appendChild(titleText);
node.classList.add("editable-title");
// This breaks the code once there are multiple components in the document
document.getElementsByTagName("span")[0].appendChild(node);
},
How can I scope this in an efficient way? Thank you very much in advance!
Well, with Vue, you'll probably want to avoid creating DOM elements the "native" way whenever possible, as you might run into race condition where Vue is unaware of the existence of these elements which you probably want be reactive at some point in time (in your case, the <span> double-clicking).
What you could do instead, is perhaps to dynamically "switch between" these different headings with this <component> and the v-bind:is prop. Consider the following example:
Vue.component('EditableHeading', {
template: '#editable-heading',
props: {
size: {
type: String,
default: 'h1'
},
value: {
type: String,
required: true
}
},
data() {
return {
editing: false
}
},
methods: {
confirm(e) {
this.$emit('input', e.target.value);
this.close();
},
start() {
this.editing = true;
this.$nextTick(() => {
this.$el.querySelector('input[type="text"]').select();
});
},
close() {
this.editing = false;
}
}
})
new Vue({
el: '#app',
data: () => ({
titleList: [],
text: 'New Title',
size: 'h3'
}),
methods: {
addNewTitle() {
this.titleList.push({
text: this.text,
size: this.size
});
}
}
})
.edit-on-click {
user-select: none;
}
.heading-size {
margin-top: 1rem;
width: 24px;
}
p.info {
background-color: beige;
border: 1px solid orange;
color: brown;
padding: 4px 5px;
margin-top: 2rem;
}
<script src="https://vuejs.org/js/vue.min.js"></script>
<div id="app">
<editable-heading
v-for="(title, index) of titleList" :key="index"
v-model="title.text"
:size="title.size">
</editable-heading>
<div>
<label>
Heading size:
<input v-model="size" class="heading-size" />
</label>
</div>
<div>
<label>
Title:
<input v-model="text" />
</label>
</div>
<div>
<button #click="addNewTitle()">Add new title</button>
</div>
<p class="info">
[double-click]: Edit <br />
[enter]: Confirm <br />
[esc/mouseleave]: Cancel
</p>
</div>
<script id="editable-heading" type="text/x-template">
<div class="edit-on-click">
<input
type="text"
v-if="editing"
:value="value"
#blur="close"
#keydown.enter="confirm"
#keydown.esc="close" />
<component :is="size" v-else #dblclick="start">{{value}}</component>
</div>
</script>

Not working with vaadin-grid and context menu

I am new to polymer. With the following code I am trying to display a dialog box on context menu click. But the code does not work. Can someone help?
<dom-module id="my-dom">
<template>
<style is="custom-style">
vaadin-grid {
--vaadin-grid-body-cell: {
padding: 0;
};
}
.body-content {
padding: 8px;
}
</style>
<vaadin-context-menu>
<template>
<paper-listbox>
<paper-item on-tap="_add">Add</paper-item>
<template is="dom-if" if="[[_isGridBody(target)]]">
<paper-item content="[[target]]" on-tap="_remove">Remove</paper-item>
</template>
</paper-listbox>
</template>
<vaadin-grid id="grid" items="[[items]]">
<vaadin-grid-column>
<template class="header">Name</template>
<template>
<div class="body-content" data-index$="[[index]]">[[item.name]]</div>
</template>
</vaadin-grid-column>
<vaadin-grid-column>
<template class="header">Surname</template>
<template>
<div class="body-content" data-index$="[[index]]">[[item.surname]]</div>
</template>
</vaadin-grid-column>
<vaadin-grid-column>
<template class="header">Effort</template>
<template>
<div class="body-content" data-index$="[[index]]">[[item.effort]]</div>
</template>
</vaadin-grid-column>
</vaadin-grid>
</vaadin-context-menu>
<vaadin-dialog id="dialog" no-close-on-esc no-close-on-outside-click>
<template>
<div>Press any button to close</div>
<br>
<isp-content></isp-content>
<vaadin-button on-click="closeDialog">Ok</vaadin-button>
<vaadin-button on-click="closeDialog">Cancel</vaadin-button>
</template>
</vaadin-dialog>
<!--paper-dialog id="dialog" modal>
<h2>Dialog Title</h2>
<p> Hello World</p>
<div class="buttons">
<paper-button dialog-confirm autofocus>Tap me to close</paper-button>
</div>
</paper-dialog-->
</template>
<script>
Polymer({
is: "my-dom",
properties: {
items: {
type: Array,
value: function() {
return getItems();
}
}
},
ready: function() {
console.log("my-dom ready");
},
_isGridBody: function(target) {
return target.classList.contains("body-content");
},
_add: function() {
this.unshift('items', getNewItem());
this.$.dialog.open();
},
_remove: function(e) {
var index = parseInt(e.target.content.getAttribute('data-index'));
this.splice('items', index, 1);
},
closeDialog: function() {
this.$.dialog.opened = false;
}
});
function getNewItem() {
function random(arr) {
return arr[Math.floor(Math.random() * arr.length)];
}
var names = ['Artur', 'Patrik', 'Henrik', 'Teemu'];
var surnames = ['Signell', 'Lehtinen', 'Ahlroos', 'Paul'];
return {
name: random(naisp-contentmes),
surname: random(surnames),
effort: Math.floor(Math.random() * 6)
};
}
function getItems() {
var items = [];
for (var i = 0; i < 100; i++) {
items.push(getNewItem());
}
return items;
}
</script>
</dom-module>
With the above code I am trying to display a vaadin-dialog box on right-click the context menu. The dialog box appears for the first time. But after that the context-menu does not show at all.

Why my array gets overlaped

I have a simple project where i am trying to learn the concepts of vue.js using componenetes, comunication between components(i use eventBus) i am using the webkit-simple template to approach this, basicly what happens, is that i have 1 component that consists in a simple textarea where i add some text, that text should be displayed in my second component, that is a template where i render a array with all my texts that i inserted, like a list of quotes.
component addQuote
<template>
<div class="row">
<div class="col-md-12">
<div class="form-group">
<div class="col-md-offset-3 col-md-6">
<label>Quote:</label>
<textarea v-model="quote.text" class="form-control" rows="5"></textarea>
<div class="text-center">
<button #click="addQuote" class="btn btn-primary center">Add Quote</button>
</div>
</div>
</div>
</div>
</div>
</template>
<script>
import { quoteBus } from '../main.js';
export default {
methods: {
addQuote() {
if (this.counter < 10) {
this.counter++;
this.quote.key =+ new Date();
quoteBus.$emit('saveQuote', this.quote);
}
}
},
data: function () {
return {
quote: {},
counter: 0
}
},
created(){
quoteBus.$on('decreaseCounter', () => {
this.counter--
});
}
}
</script>
<style scoped>
.row {
margin-top: 40px;
}
.center {
margin: 0 auto;
}
div .text-center {
margin-top: 20px;
}
</style>
component quotes
<template>
<div class="row">
<div class="col-md-3" v-for="(quote,$index) in quotes" #click="deleteQuote($index)" :key="quote.key">
<div class="spacing">
<h2>{{quote.text}}</h2>
</div>
</div>
</div>
</template>
<script>
import { quoteBus } from '../main.js';
export default {
data: function () {
return {
quotes: []
}
},
methods: {
deleteQuote(i){
this.quotes.splice(i,1);
quoteBus.$emit('decreaseCounter');
}
},
created() {
quoteBus.$on('saveQuote', quote => {
this.quotes.unshift(quote);
console.log(JSON.stringify(this.quotes));
});
}
}
</script>
<style scoped>
h2 {
font-family: 'Niconne', cursive;
}
div .col-md-3 {
border: 1px solid darkgray;
padding: 10px;
}
div .row {
margin-top: 40px;
}
.spacing {
margin: 10px;
padding: 10px;
}
</style>
the problem is, everytime i add a quote the text replace all the elements before.
Example:
9th entry: text: "abcdef", all the entries in the array has this value in text, all my divs has the value of abcdef, what is happening :S
const quoteBus = new Vue();
Vue.component('addQuote', {
template: '#addQuote-template',
methods: {
addQuote() {
if (this.counter < 10) {
this.counter++;
this.quote.key = +new Date();
quoteBus.$emit('saveQuote', Object.assign({}, this.quote));
}
}
},
data: function() {
return {
quote: {},
counter: 0
}
},
created() {
quoteBus.$on('decreaseCounter', () => {
this.counter--
});
}
});
Vue.component('quotes', {
template: '#quotes-template',
data: function() {
return {
quotes: []
}
},
methods: {
deleteQuote(i) {
this.quotes.splice(i, 1);
quoteBus.$emit('decreaseCounter');
}
},
created() {
quoteBus.$on('saveQuote', quote => {
this.quotes.unshift(quote);
console.log(JSON.stringify(this.quotes));
});
}
});
new Vue({
el: '#app'
});
<script src="//cdnjs.cloudflare.com/ajax/libs/vue/2.2.4/vue.min.js"></script>
<template id="addQuote-template">
<div class="row">
<div class="col-md-12">
<div class="form-group">
<div class="col-md-offset-3 col-md-6">
<label>Quote:</label>
<textarea v-model="quote.text" class="form-control" rows="5"></textarea>
<div class="text-center">
<button #click="addQuote" class="btn btn-primary center">Add Quote</button>
</div>
</div>
</div>
</div>
</div>
</template>
<template id="quotes-template">
<div class="row">
<div class="col-md-3" v-for="(quote,$index) in quotes" #click="deleteQuote($index)" :key="quote.key">
<div class="spacing">
<h2>{{quote.text}}</h2>
</div>
</div>
</div>
</template>
<div id="app">
<add-quote></add-quote>
<quotes></quotes>
</div>
The problem is that there is only one instance of this.quote in your addQuote component. You pass that particular object to quotes to be put into the array every time. When an object is put into an array, it is by-reference. If you put the same object into an array multiple times, you just have multiple references to the object's contents. Every element of your array is a reference to the same set of contents.
You need to send a copy of the object instead:
quoteBus.$emit('saveQuote', Object.assign({}, this.quote));

Categories

Resources