I have a list of attachments that can be removed from a page via a button (see image attachment).
However, the deletion order is not correct. Sometimes when you click a button, a different attachment is deleted and not the one that you thought you clicked.
What's happening? Does anyone know how I can fix?
Full demo code here: codesandbox
EditPost.vue:
<li>Media Attachments
<ul v-if="attachmentsFileNames && attachmentsFileNames.length">
<li v-for="(attachmentFileName, index) in attachmentsFileNames" :key="index">
<a :href="'will-be-download-Attachment-Api-Url/' + attachmentsArray.attachments[index]">
{{ attachmentFileName }}
</a>
<button #click.prevent="removeAttachmentItemEvent(attachmentsArray.attachments[index])">Delete me!</button>
</li>
</ul>
</li>
methods: {
removeAttachmentItemEvent(item) {
this.attachmentsToDelete.push(item);
this.$emit("removeMediaAttachment", item);
},
}
PostDetail.vue:
<section v-if="editPostFormIsVis">
<EditPost
:post="post"
#update="postUpdated"
#cancel="cancelEdit"
:attachmentsArray="attachmentsArray"
#removeMediaAttachment="removeMediaItem"
/>
</section>
data() {
return {
post: {},
attachmentsArray: [],
attachmentsToDelete: []
};
},
...
methods: {
removeMediaItem(item) {
this.attachmentsArray.attachments.splice(item, 1);
},
}
removeMediaItem(index) {
this.attachmentsArray.attachments.splice(index, 1);
},
Pass the index in removeMediaItem emitted event method.
Related
So the problem I'm facing is that I have a template that fetches data from a Realtime Firebase Database and at the same time the user can import more data through an input element. I'm using Vue.js and I need the data to be bound to each other.
Here is my template:
<template>
<ul>
<li>
<input type="text" v-model="email" v-on:keyup.enter="addData()"/>
<img #click="addData()" src="#/assets/Plus.png" />
</li>
</ul>
<ul>
<li v-for="(item, key) in emails" :key="key">
<div>
<p>{{ item.data }}</p>
<img #click="deleteDataByKey(key)" src="#/assets/Delete.png" />
</div>
<div class="first">
<input type="text" v-model="comment[key]" v-on:keyup.enter="addComment(key, comment[key])"/>
<img #click="addComment(key, comment[key])" src="#/assets/Plus.png" />
</div>
<div class="second">
<p>{{ comment[key] }}</p>
<img #click="deleteCommentByKey(key)" src="#/assets/Delete.png" />
</div>
</li>
</ul>
</template>
Now what is happening is that I want to show <div class="first"> when there is no comment and when there is one, <div class="second"> should be shown while hiding the first one.
I tried using v-if="comment[key]" but it will toggle the divs straight away.
I also tried to v-model.lazy which seems to be working but then the method to update the db is not called.
I tried using pure JS in the method to change the HTML but it doesn't seem to be working as well.
These are my methods and data:
data() {
return {
emailList: [],
email: "",
comment: []
};
},
addData() {
db.ref("emailItems").push({
data: data
});
this.email = "";
this.fetchData();
},
deleteDataByKey(key) {
db.ref("emailItems"+key).remove();
this.fetchData();
},
addComment(key, comment) {
db.ref(`emailItems/${key}/comment`).set(comment);
},
deleteCommentByKey(key){
db.ref("comment/"+key).remove();
this.fetchData();
},
fetchData() {
db.ref("emailItems")
.once("value")
.then(snapshot => {
this.emailList = snapshot.val().emailItems;
});
}
And the db structure looks like this
Any help would be highly appreciated...
I think you should build more on the (arguably) biggest features of Vue, namely: reactivity & components.
Break down the logic a bit more, until you arrive at elements that do only one thing - those elements can be your components. Then build up the business logic from those atomic components.
Vue.component('NewEmail', {
data() {
return {
email: null,
}
},
methods: {
handleKeyup() {
this.$emit("add-email", {
email: this.email,
comment: null
})
this.email = null
}
},
template: `
<div>
<label>
NEW EMAIL: <input
type="email"
placeholder="Type in an email"
v-model="email"
#keyup.enter="handleKeyup"
/>
</label>
</div>
`
})
Vue.component('SingleEmailRow', {
props: {
email: {
type: Object,
default: null,
}
},
methods: {
handleDeleteClick() {
this.$emit('remove-email', this.email)
},
},
template: `
<li
class="d-flex"
>
<div>
{{ email.email }}
</div>
<div>
<button
#click="handleDeleteClick"
>
X
</button>
</div>
<component
:is="email.comment ? 'HasEmailComment' : 'NoEmailComment'"
:email="email"
v-on="$listeners"
></component>
</li>
`
})
Vue.component('HasEmailComment', {
props: {
email: {
type: Object,
required: true
}
},
methods: {
handleUpdateComment() {
this.$emit('update:comment', { ...this.email,
comment: null
})
}
},
template: `
<div
class="d-flex"
>
<div>
{{ email.comment }}
</div>
<button
title="Remove comment"
#click="handleUpdateComment"
>
-
</button>
</div>
`
})
Vue.component('NoEmailComment', {
props: {
email: {
type: Object,
required: true
}
},
data() {
return {
comment: null,
}
},
methods: {
handleUpdateComment() {
this.$emit('update:comment', { ...this.email,
comment: this.comment
})
}
},
template: `
<div
class="d-flex"
>
<div>
<input
v-model="comment"
type="text"
placeholder="Write a comment"
/>
</div>
<button
title="Add comment"
#click="handleUpdateComment"
>
+
</button>
</div>
`
})
new Vue({
el: "#app",
data() {
return {
emailList: [],
}
},
methods: {
handleAddEmail(newEmail) {
if (!this.emailList.find(({
email
}) => email === newEmail.email)) {
this.emailList.push(newEmail)
}
},
handleRemoveEmail(toRemove) {
this.emailList = this.emailList.filter(({
email
}) => {
return email !== toRemove.email
})
},
handleUpdateComment(newEmail) {
this.emailList = this.emailList.map(email => {
if (email.email === newEmail.email) {
return newEmail
} else {
return email
}
})
}
}
})
.d-flex {
display: flex;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<new-email #add-email="handleAddEmail"></new-email>
<ul v-for="(email, i) in emailList" :key="i">
<single-email-row :email="email" #remove-email="handleRemoveEmail" #update:comment="handleUpdateComment"></single-email-row>
</ul>
</div>
OK, the comment handling in SingleEmailRow could be put to its separate component (based on my thoughts above).
The main points in the snippet above are:
there's only one original data source (emailList in the root component) that is passed down as props where needed, and all the other components & functions just manipulate THAT list via events (reactivity is great!)
because all the components are based on one central data source, they just have to work with the item they get as props. (Ok, they have some internal state, but hey - this is only a snippet! :) )
the two components have only one responsibility:
NewEmail: to add an item to the central emailList
SingleEmailRow: to manage the data in ONE email item
I hope this helps you a bit to reach a solution for your problem.
EDIT: UPDATING SNIPPET
I had to update the snippet, because I wasn't satisfied with it.
Modifications:
adding two new components: HasEmailComment, NoEmailComment
updated SingleEmailRow with the two new components
Now, all the components have only one task to do.
i want to show delete button for each comment that a particular user made, but i am unable to do it, i am using v-bind to disable delete buttton for others comment, so that user cant delete others comment, but its still visible for all the comments i.e (for other users as well ). suppose i am a user i comment 3 times then delete button should show on my comments only not on others comment. can some one please help me to achieve this?
My code snippet is below
<div class="comment-list" v-for="comment in comments" :key="comment._id">
<div class="paragraph" :class="[name, onCaretButtonClick ? 'paragraph' : 'hide']">
<span class="names" id="comment-desc">
<label class="comm-name">{{ comment.name }}</label>
<button class="like-btn" id="like-comment"><i class="fas fa-heart"></i></button>
<label> {{ comment.likes + ' likes' }} </label>
<button v-bind:disabled="isCommentIdMatched" v-on:click="deleteComment(comment.userId)"
class="del-btn">delete</button>
</span>
<p>{{ comment.comment }}</p>
</div>
</div>
below is deleteComment function and computed properties that i am using
computed: {
comments() {
return store.state.comments
},
getUserId() {
return store.state.user._id
},
isCommentIdMatched() {
let comments = store.state.comments
let value = false
if(comments) {
comments.forEach((comment) => {
if(comment.userId.match(this.getUserId)) {
value = true
}
})
return value
}
else {
return value
}
}
},
methods: {
deleteComment: function(commentUserId) {
let userId = store.state.user._id
if(commentUserId.match(userId)) {
console.log('yes matched')
}
},
}
there is no need to write isCommentIdMatched property, instead you can use change some lines.
add v-bind="comments" in below line
<div v-bind="comments" class="comment-list" v-for="comment in comments" :key="comment._id">
and add v-if="comment.userId && comment.userId.match(getUserId)" in below button
<button v-if="comment.userId && comment.userId.match(getUserId)" v-on:click="deleteComment(comment.userId)" class="del-btn">delete</button>
I am building a live search component with Vuejs. The search is working as expected. On keyup the method populates an unordered list. I am at a loss but need to:
Click on a selection from the search results and populate the input with that name.
Get the selected id.
Any suggestions?
The View
<label>Vendor:</label>
<input type="text" v-model="vendor" v-on:keyup="get_vendors">
<div class="panel-footer" v-if="vendors.length">
<ul class="list-group">
<li class="list-group-item for="vendor in vendors">
{{ vendor.vendor_name }}
</li>
</ul>
</div>
The Script
export default {
data: function() {
return {
vendor:'',
vendors: []
}
},
methods: {
get_vendors(){
this.vendors = [];
if(this.vendor.length > 0){
axios.get('search_vendors',{params: {vendor: this.vendor}}).then(response =>
{
this.vendors = response.data;
});
}
}
}
}
</script>
The Route
Route::get('search_vendors', 'vendorController#search_vendors');
The Controller
public function search_vendors(Request $request){
$vendors = vendor::where('vendor_name','LIKE','%'.$request->vendor.'%')->get();
return response()->json($vendors);
}
This is what I came up with. Works nicely.
The View
<label>Vendor:</label>
<input type="text" v-model="vendor" v-on:keyup="get_vendors" class="col-xl-6 form-control ">
<div class="panel-footer autocomplete-box col-xl-6">
<ul class="list-group">
<li v-for="(vendor,id) in vendors" #click="select_vendor(vendor)" class="list-group-item autocomplete-box-li">
{{ vendor.vendor_name }}
</li>
</ul>
</div>
The Script
export default {
data: function() {
return {
vendor:'',
vendor_id:'',
vendors: []
}
},
methods: {
select_vendor(vendor){
this.vendor = vendor.vendor_name
this.vendor_id = vendor.id
this.vendors = [];
},
get_vendors(){
if(this.vendor.length == 0){
this.vendors = [];
}
if(this.vendor.length > 0){
axios.get('search_vendors',{params: {vendor: this.vendor}}).then(response => {
this.vendors = response.data;
});
}
},
},
}
</script>
The Route
Route::get('search_vendors', 'vendorController#search_vendors');
The Controller
public function search_vendors(Request $request){
$vendors = vendor::where('vendor_name','LIKE','%'.$request->vendor.'%')->get();
return response()->json($vendors);
}
The CSS
.autocomplete-box-li:hover {
background-color: #f2f2f2;
}
.autocomplete-box{
position: absolute;
z-index: 1;
}
If you want to get the id of the vendor you can do it using vendor.vendor_id which should be present in the vendor object which is returned from the server and if you want to populate the vendors array with new options you can have a watch or a #change (binding) function to add the entered text field as the new vendor in the vendor array. Also, I would suggest that you download the vue extension as it will help you a lot in debugging
<label>Vendor:</label>
<input type="text" v-model="vendor" v-on:keyup="get_vendors">
<div class="panel-footer" v-if="vendors.length">
<ul class="list-group">
<li class="list-group-item for="(vendor,index) in vendors">
{{ index }}
{{ vendor.vendor_id }}
{{ vendor.vendor_name }}
</li>
</ul>
</div>
I want to dynamically apply a CSS style to transform:translateY a menu context (div) whenever it is clicked near the bottom of the page so that it opens upwards and fits inside the page.
This is the element FileContectMenu.vue:
<template>
<el-popover
ref="contextMenuPopover"
placement="top-start"
trigger="manual"
v-model="value"
:visible-arrow="false">
<ul
v-for="(folder, index) in items"
:key="index"
class="u-list-unstyled">
<li
v-for="item in folder"
:key="item.action">
<a class="u-block"
#click.stop="click($event, item)">
{{ item.label }}
</a>
</li>
<hr class="u-mt2 u-mb2" />
</ul>
<ul class="u-list-unstyled">
<li key="close">
<a class="u-block"
#click.stop="close">
Close
</a>
</li>
</ul>
</el-popover>
</template>
<script>
export default {
name: 'FileContextMenu',
props: {
items: {
type: Array,
required: true
},
value: {
type: Boolean
}
},
mounted () {
document.addEventListener('mouseup', this.handleDocumentClick)
},
beforeDestroy () {
document.addEventListener('mouseup', this.handleDocumentClick)
},
methods: {
click (event, item) {
this.$emit('contextmenuclick', item.action, item)
this.close()
},
handleDocumentClick (event) {
const popover = this.$refs.contextMenuPopover
if (popover) {
const popoverElement = popover.$el
if (popoverElement && popoverElement.contains(event.target)) {
let clickPosition = event.screenY
let windowHeight = window.innerHeight
let distanceTillBottom = windowHeight - clickPosition
if (distanceTillBottom < 130) {
console.log('Apply style "transform:translateY(-300px)"') // <-- Apply the CSS transfor
}
return
} else {
this.close()
}
}
},
close () {
this.$emit('input', false)
}
}
}
</script>
Inside the if (distanceTillBottom < 130) I want to apply the CSS transform to the el-popover element. Something like: style = "transform:translateY(-300px)" but I can't manage to do it.
Any insights on how to go about this, please?
this.$refs.contextMenuPopover.$refs.popper.style.transform = 'translateY(-250px)'
Looping out a number of boxes within the same component in vuejs.
Each box has a button that reveals more text using v-on:click.
The problem is that all the boxes respond to this at the same time.
Is there a way to target the specific button being clicked if there are several buttons in a component?
Is there some way to isolate each button so they all dont activate at once?
<div class="filter-list-area">
<button #click="show =!show"> {{text}} </button>
<ul class="filter-list-item-area">
<li class="filter-list-item " v-for="(items, key) in packages">
<div>
<img class="fit_rating">
</div>
<div class="filter-list-item-info" >
<h3>{{items.partner_university}}</h3>
<p> Match: {{items.match_value}}</p>
<div v-for="(courses, key) in courses">
<transition name="courses">
<div class="courses2" v-show="show">
<p v-if="courses.Pu_Id === items.pu_Id">
{{courses.Course_name}}
</p>
</div>
</transition>
</div>
</div>
</li>
</ul>
</div>
</template>
<script>
import testdata from '../App.vue'
export default {
data (){
return{
text: 'Show Courses',
testFilter: 'Sweden',
show: false
}
},
props: {
title: String,
likes: Number,
isPublished: Boolean,
commentIds: Array,
author: Object,
testuni: Array,
list: Array,
packages: Array,
courses: Array
},
methods:{
afunction(){
console.log(this.show);
},
changeText(){
if(this.show){
this.text = 'Hide Courses'
}
else{
this.text = "Show Courses"
}
}
},
mounted() {
this.afunction();
},
watch: {
show:
function() {
this.afunction()
this.changeText()
}
},
}
EDIT: I've created this before you posted the code example, but you could use same principle:
In your data add showMoreText, which will be used to track if show more data should be shown.
I would agree with #anaximander that you should use child components here
Simple example how to show/hide
<template>
<div>
<div v-for="(box, index) in [1,2,3,4,5]">
<div>
Box {{ box }} <button #click="toggleMore(index)">More</button>
</div>
<div v-show="showMoreText[index]">
More about box {{ box }}
</div>
</div>
</div>
</template>
<script>
export default {
data() {
return {
showMoreText: {},
}
},
methods: {
toggleMore(index) {
/*
Adds a property to a reactive object, ensuring the new property is
also reactive, so triggers view updates.
https://vuejs.org/v2/api/#Vue-set
*/
this.$set(this.showMoreText, index, ! this.showMoreText[index])
}
}
}
</script>
This sounds like an ideal situation for a new child component, which will allow each instance of the new component to have its own separate state.
The child components can emit events to the parent, if cross-component communication is necessary.