Remove listing from Vuejs1 vs Vuejs2 - vue.js

Currently I am using Vuejs2 to generate listing for image upload, which will show preview image after select an image file, here is my code written in codepan with Vuejs2:
https://codepen.io/anon/pen/MELZKe
<div v-for="(image, index) in images" :key="index">
<div class="image-upload">
<label v-bind:for="'cover-upload-' + index">
<img src="http://via.placeholder.com/100x100" width="100" class="preview-img"/>
</label>
<input v-bind:id="'cover-upload-' + index" v-bind:name="'slides[' + index + ']'" type="file" class="upload-preview" style="display:none">
</div>
<button type="button"
#click.prevent="removeImage(index)"
v-show="image_quantity > 1">
Remove
</button>
</div>
I created 2 file upload images, upload first image (will preview your image), delete the first preview image, but it will delete the last preview image instead of first one.
However, almost same coding but using Vuejs1, it will delete the first preview image instead of last one. Here is codepen with Vuejs1:
https://codepen.io/anon/pen/VMgqbG
I have no idea how to write such situation for Vuejs2 or I have to stick with Vuejs1?
Thanks.

Using the index of an array as the key is a bad idea. This is because indexes mutate. Use a proper key and your issue is resolved.
Think about the case in the original code where there are two images in the images array. At first, the index of image one is zero and the index of the second image is one. When you delete the first image, now the index of what was originally was the second image has changed to zero, but Vue has existing DOM elements for key zero that show the image for the former object that was at index zero. Since Vue has those DOM elements for key zero, it re-uses them and it appears as though the first image was not deleted.
console.clear()
new Vue({
el: '#content',
data: {
images: [{ name: '' , id: 1}],
},
computed: {
image_quantity: function () {
return this.images.length;
},
},
methods: {
removeImage: function (index) {
this.images.splice(index, 1);
},
addImage: function (event) {
event.preventDefault();
this.images.push({
name: '',
id: Math.max(...this.images.map(i => i.id)) + 1
});
}
}
});
function readURL(input, preview) {
if (input.files && input.files[0] && input.files[0].type.match('image.*')) {
var reader = new FileReader();
reader.onload = function (e) {
$(preview).attr('src', e.target.result);
}
reader.readAsDataURL(input.files[0]);
}
}
$(document).on('change', '.upload-preview', function () {
readURL(this, $(this).prev().find('.preview-img'));
});
<div id="content">
<div v-for="(image, index) in images" :key="image.id">
<div class="image-upload">
<label v-bind:for="'cover-upload-' + index">
<img src="https://via.placeholder.com/100x100" width="100" class="preview-img"/>
</label>
<input v-bind:id="'cover-upload-' + index" v-bind:name="'slides[' + index + ']'" type="file" class="upload-preview" style="display:none">
</div>
<button type="button"
#click.prevent="removeImage(index)"
v-show="image_quantity > 1">
Remove
</button>
</div>
<button type="button" v-on:click="addImage">Create New</button>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue"></script>
<script src="https://code.jquery.com/jquery-2.2.4.min.js"></script>

Related

Vue does not correctly remove item from vfor

I have this custom component in vue callled "dm-vehicle-spec"
<dm-vehicle-spec #_handleRemoveSpec="_handleRemoveSpec" v-for="spec, index in vehicleSpecs" :key="index" :index="index" :spec="spec"></dm-vehicle-spec>
which looks like the following
<script>
export default {
props: ["spec"],
data() {
return {
specName: null,
specValue: null,
}
},
mounted() {
if (this.spec.detail_name && this.spec.detail_value) {
this.specName = this.spec.detail_name;
this.specValue = this.spec.detail_value;
}
},
computed: {
getSpecNameInputName() {
return `spec_${this.spec.id}_name`;
},
getSpecValueInputName() {
return `spec_${this.spec.id}_value`;
},
},
methods: {
_handleRemoveSpec() {
this.$emit("_handleRemoveSpec", this.spec.id);
}
},
}
</script>
<template>
<div class="specs-row flex gap-2 w-full items-center">
<div class="col-1 w-5/12">
<input placeholder="Naam" type="text" :id="getSpecNameInputName" class="w-full h-12 spec_name rounded-lg border-2 border-primary pl-2" v-model="specName">
</div>
<div class="col-2 w-5/12">
<input placeholder="Waarde" type="text" :id="getSpecValueInputName" class="w-full h-12 spec_name rounded-lg border-2 border-primary pl-2" v-model="specValue">
</div>
<div #click="_handleRemoveSpec" class="col-3 w-2/12 flex items-center justify-center">
<i class="fas fa-trash text-lg"></i>
</div>
</div>
</template>
so when i have 3 specs, 1 from the database and 2 customs i have the following array vehicleSpecs (Which i loop over)
[
{"id":23,"vehicle_id":"1","detail_name":"Type","detail_value":"Snel","created_at":"2022-11-07T19:06:26.000000Z","updated_at":"2022-11-07T19:06:26.000000Z","deleted_at":null},
{"id":24},
{"id":25}
]
so lets say i want to remove the second item from the list so the one with test1 as values, then the array looks like
[{"id":23,"vehicle_id":"1","detail_name":"Type","detail_value":"Snel","created_at":"2022-11-07T19:06:26.000000Z","updated_at":"2022-11-07T19:06:26.000000Z","deleted_at":null},{"id":25}]
So the second array item is removed and thats correct because object with id 24 no longer exsist but my html shows
that the value for object with id 24 still exists but the value for object with id 25 is removed, how is that possible?
If u need any more code or explaination, let me know
Any help or suggestions are welcome!
Index is not a good choice to use as v-for key.
Indexes are changing when you delete something from array.
Try using another property as a key.
Run the loop in reverse order. This way, deleted items do not effect the remaining item indexes
Hit: indexReverse = list.length - index

vue.js - Add a placeholder in text box with click of a button

I have a textbox
<div>
<b-form-textarea
id="textarea"
v-model="text"
placeholder="Certification text "
rows="5"
max-rows="6"
></b-form-textarea>
</div>
And two buttons
<b-button variant="primary" class="btn btn-primary btn-lg top-right-button mr-1">Save</b-button>
<b-button variant="info" class="btn btn-info btn-lg top-right-button mr-1">Add Name</b-button>
When the user is typing and in between if he clicks Add Name button {name} should be placed in the textbox (Where ever the cursor is).
How can i implement it?
You can add a ref to your textarea, and access the selectionStart property, which contains the placement of the cursor.
You can then use this index to splice the given text into the textarea's text at the given position.
And since clicking the button will lose the focus of the input, you can add it back by calling the focus method on the ref, along with setting the selectionStart and selectionEnd so the cursor is where it left off.
new Vue({
el: "#app",
data() {
return {
text: ""
};
},
methods: {
addName() {
const { text } = this;
const textarea = this.$refs["my-textarea"].$el;
const index = textarea.selectionStart;
const name = "{name}";
this.text = `${text.substring(0, index)}${name}${text.substring(
index
)}`;
textarea.focus();
setTimeout(() => {
textarea.selectionStart = index + name.length;
textarea.selectionEnd = index + name.length;
});
}
}
});
<script src="https://unpkg.com/vue#2.6.12/dist/vue.min.js"></script>
<script src="https://unpkg.com/bootstrap-vue#2.17.3/dist/bootstrap-vue.js"></script>
<link href="https://unpkg.com/bootstrap#4.5.2/dist/css/bootstrap.min.css" rel="stylesheet" />
<link href="https://unpkg.com/bootstrap-vue#2.17.3/dist/bootstrap-vue.css" rel="stylesheet" />
<div id="app">
<b-form-textarea ref="my-textarea" v-model="text" placeholder="Certification text" rows="5" max-rows="6">
</b-form-textarea>
<b-btn variant="primary" size="lg" #click="addName">
Add Name
</b-btn>
</div>

vue.js basic function not working as wanted

I have create a set of pairs of divs, that use the v-for method to get data from a set of dummy objects in an array. The goal is for each pair of divs when I click on the visible div it opens the corresponding relevant div. At the moment my function which I have attached as a property of a method object only opens the invisible div of the first pair of divs even if I click on the 3rd visible div it still displays the invisible div. i am using the vue framework.
I have attached pictures of my code then the actual code.
[The div I am trying to open is session-on-click details atm it is opening only that for the first index][1]
<div class = "rowuserlog" id="log-container" v-for="session in sessions" :key="session.id"
>
<div id="log-container-user-row-1">
<div id="profile-log-title" #click="logToggler()"> {{session.name}} </div>
<div id="profile-log-date"> {{session.date}}</div>
<div id="user-log-metric-container">
<div id ="user-log-hr" class="log-metric">{{session.hr}}</div>
<div id ="user-log-time" class="log-metric"> {{session.time}}</div>
<div id ="user-log-meters" class="log-metric"> {{session.distance}} </div>
<div id="session-onclick-details">
<div id="log-comments">
{{session.comments}}
</div>
<div id="log-edit-buttons">
<button id="log-save" class="log-edit-button"> Save </button>
<button id="log-delete" class="log-edit-button"> Delete </button>
<button id="log-cancel" class="log-edit-button"> Cancel </button>
<button id="log-edit" class="log-edit-button"> Edit </button>
</div>
</div>
```
My method
The toggler is the method I want:Here is the code
methods:{
logToggler () {
const extraInfoLog = document.getElementById("session-onclick-details");
return extraInfoLog.style.display="block";
}
]
[2]
[1]: https://i.stack.imgur.com/ddrYo.png
[2]: https://i.stack.imgur.com/evriF.png
data() {
return {
sessions:[
{ name:'test',
value:false,
id:1
},
{ name:'test1',
value:false,
id:2
},
{ name:'test2',
value:false,
id:3
}
]
}
}
methods:{
logToggler(_index) {
for(let index=0;index<sessions.length;index++) {
if(index == _index)
sessions[index].value = true
else
sessions[index].value = false
}
}
}
Template
<div class = "rowuserlog" id="log-container" v-for="(session,index) in sessions" :key="session.id"
>
<div id="profile-log-title" #click="logToggler(index)" v-if="session.value"> {{session.name}} </div>

Binding to array fields in vue.js

I want to make multiple 'in-place' editable date fields in table rows.
An example for a single field below works.
I show the currentdate (oldDate) as a label. User clicks on 'Change', an input field appears, after editing the user can Accept or Cancel.
https://jsfiddle.net/asrajan55/qv6crg84/
<div id="root">
<label>Test Date:</label>
<span v-show="!makeEditable"> {{ oldDate }} </span>
<span v-show = "makeEditable">
<input type="date" v-model="newDate" required=""/>
<button #click="acceptClicked">Accept</button>
<button name="cancel" #click="makeEditable=false">Cancel</button>
</span>
<button v-show="!makeEditable" #click="makeEditable=true" >Change</button>
</div>
new Vue({
el: "#root",
data: {
oldDate: '2019-02-04',
newDate: '2019-02-04',
makeEditable: false,
},
methods: {
acceptClicked(){
if (this.newDate!='') {
this.oldDate=this.newDate;
this.makeEditable=false;
}
}
}
});
However if I try multiple(2) fields the click event fires (sometimes) but nothing seems to happen. No errors in console. Also the Vue debugger in the browser does not immediately update the changed fields. Please help. I am desperate and pulling my hair out!
https://jsfiddle.net/asrajan55/9uhkr4w0/3/
<div id="root">
<div v-for="(item,index) in oldDates">
<label for="">Test Date:</label>
<span v-show="!editables[index]">{{item}}</span>
<input v-show="editables[index]" type="date" v-model="oldDates[index]"/>
<button v-show="editables[index]">Accept</button>
<button v-show="editables[index]" #click="editables[index]=false">Cancel</button>
<button v-show="!editables[index]" #click="makeEditable(index)">Change</button>
<hr />
</div>
</div>
new Vue({
el: "#root",
data: {
oldDates: ['2019-01-04', '2019-02-04'],
newDates: ['2019-01-04', '2019-02-04'],
editables: [false, false]
},
methods: {
makeEditable(index) {
alert(index);
this.editables[index] = true;
}
}
});
The problem was that you were mutating the array in place,
creating a new array reference and passing it would solve the issue
fixed here : https://jsfiddle.net/e3L2zcna/
makeEditable(index) {
this.editables = this.editables.map((val,i) => i===index || val);
}
Try using this.$set(this.editables, index, true);
Vue can't detect changes to arrays if you directly access an element using []. Read about it here:
https://vuejs.org/2016/02/06/common-gotchas/#Why-isn%E2%80%99t-the-DOM-updating

Vuejs - Proper way to clone an element, and append to DOM

I have a HTML input field to enter some information:
<div id="fruitForm">
<div class="inputArea">
<label for="fruit0">Enter Fruit Name</label>
<input id="fruit0"></input>
</div>
</div>
<button #click="newInputField">Add More Fruit Input Fields</button>
<button #click="submit">Submit Fruit</button>
And then I handle that click event:
export default {
data() {
return {
}
},
methods: {
newInputField() {
//Create another input area for users to input fruits
},
submit() {
//Do something here
}
}
}
When a user selects the Add More Fruit Input Fields button, it should create a new input area so that the HTML looks like this:
<div id="fruitForm">
<div class="inputArea">
<label for="fruit0">Enter Fruit Name</label>
<input id="fruit0"></input>
</div>
<div class="inputArea">
<label for="fruit1">Enter Fruit Name</label>
<input id="fruit1"></input>
</div>
</div>
<button #click="newInputField">Add More Fruit Input Fields</button>
<button #click="submit">Submit Fruit</button>
Now, I've been using traditional DOM manipulation methods via vanilla Javascript to accomplish this... stuff like this:
const inputArea = document.getElementsByClassName('inputArea');
And then I change the id's of the input field, and then I use appendChild to add the new input field to the DOM.
So my question is: how should I be cloning this element with vuejs? I feel I'm not approaching this in the vuejs way. Should I be approaching this like a list and using v-for? Or something else?
Avoid direct DOM manipulations with Vue. You can use data property as a model for your template. The answer would be yes, you can and probably should use v-for for what you call cloning:
var demo = new Vue({
el: '#demo',
data: {
counter: 0,
inputs: [{
id: 'fruit0',
label: 'Enter Fruit Name',
value: '',
}],
},
methods: {
addInput() {
this.inputs.push({
id: `fruit${++this.counter}`,
label: 'Enter Fruit Name',
value: '',
});
}
}
});
<script src="https://unpkg.com/vue#2.5.16/dist/vue.js"></script>
<div id="demo">
<div class="inputArea" v-for="input in inputs" :key="input.id">
<label :for="input.id">{{input.label}}</label>
<input :id="input.id" v-model="input.value"></input>
</div>
<button #click="addInput">Add input</button>
</div>