vue custom emit doesnt work in v-for - vue.js

I have a component that looks like this:
<task-list :content="lesson.children" :iterator="lesson.section" :user="user" #updateTask="updateTask"></task-list>
I use component in 2 different pages. In on page its inserted just as is, in another page its parent is a v-for so it looks like:
<div class="lesson-list-item" v-bind:class="{ 'lesson-complete': course.iterator_position > index}" v-for="(lesson, index) in content" >
<task-list :content="lesson.children" :iterator="lesson.section" :user="user" #updateTask="updateTask"></task-list>
</div>
Both pages have the method:
updateTask(index){
var self = this;
this.loaded = false;
this.iterator = index;
this.newTask = index;
setTimeout(function() { self.loaded = true }, 10);
}
This is what task-list emits on a click:
cardClick(index, slug, initialIterator){
console.log(index);
if( index <= this.initialIterator){
this.$emit('updateTask', index);
}
}
The page where its not wrapped in a v-for works, the one where it is never receives the emit event.

I think your component doesn't emit an updateTask event.
Add this to the method that you want to fire the event: this.$emit('updateTask');

Related

Div doesn't appear after using v-if statement

<div class="min-h-screen flex" ref="mapDiv"></div>
<div class="bg-white absolute top-0 w-full overflow-y-auto"></div>
This is how it looks
Whenever I add to this div v-if
<div class="bg-white absolute top-0 w-full overflow-y-auto" v-if="settingsToggled"></div>
The div doesn't show up at all, even after being triggered to true. Screenshot:
I am returning settingsToggled in the setup function.
This is the code that triggers it:
export function AvatarControl(controlDiv, settingsToggled) {
const controlUI = document.createElement("div");
controlUI.style.width = "100px";
controlUI.style.margin = "10px"
controlUI.style.height = "100px";
controlUI.style.borderRadius = "8px"
controlUI.style.backgroundImage = "url('https://www.pngkey.com/png/full/114-1149878_setting-user-avatar-in-specific-size-without-breaking.png')"
controlUI.style.backgroundPosition = "center"
controlUI.innterHTML = "Avatar";
controlUI.style.zIndex = "9999";
controlDiv.appendChild(controlUI);
controlUI.addEventListener("click", () => {
console.log(settingsToggled)
settingsToggled = !settingsToggled
})
}
Anyone knows what might be the issue here?
Can I not modify the settingsToggled from outside this component?
In AvatarControl, settingsToggled appears to be a Boolean (based on settingsToggled = !settingsToggled), while your screenshot shows it as a ref, so I'm guessing you are passing the ref's value to AvatarControl like this:
// MyComponent.vue
new AvatarControl(controlDiv, settingsToggled.value)
But Booleans are passed by value, so the function can't modify the original variable like that. However, the function can modify the value of a ref argument:
// MyComponent.vue
new AvatarControl(controlDiv, settingsToggled)
// AvatarControl.js
export function AvatarControl(controlDiv, settingsToggled) {
const controlUI = document.createElement("div");
//...
controlUI.addEventListener("click", () => {
settingsToggled.value = !settingsToggled.value
})
}
}
You need a component data property for settingstoggled otherwise vue v-if hook will not find it.
Try like this in your .vue file:
data () {
return {
settingstoggled: true
}
}
You can find more info on component data here.

Callback after list leave animation and DOM update - VueJS

To do something after the DOM has been updated by Vue, you use the $nextTick binding.
To do something after a css transition has completed, you can use transitionend event.
I have a dynamic list in which things are added and removed by user actions. When removed, there is a CSS animation and then I need to check the state of the DOM immediately after the element is gone.
I was thinking that the $nextTick after the transitionend would be the state of the DOM immediately after the list item is removed, but it is not.
I need to do something after the transition has ended and the element from a list has been removed from the DOM.
Right now I have:
<transition-group class="message-bus" tag="ul" name="message-bus">
<li v-for="message in messages" v-bind:key="message.id" :ref="message.id">
<div>{{message.text}}</div>
<a class="dismiss-message" #click="dismissMessage(message)">×</a>
</li>
</transition-group>
dismissMessage(message){
const vm = this;
this.$refs[message.id][0].addEventListener("transitionend", function(){
vm.$nextTick(function(){
//This is called, but appears to be called before the element is actually removed from the DOM
//I need to query the DOM immediately after this element is removed
});
});
this.messages.splice(this.messages.indexOf(message), 1);
}
In the mounted function, I have added a MutationObserver that appears to be working as needed. I'll put this here as an answer as it does technically work and may be helpful to others, but I'm still interested in a better answer if Vue has something built in for this.
mounted(){
const vm = this;
const listItemRemoved = new MutationObserver(function(e){
if (e[0].removedNodes.length){
console.log("Removed");
}
});
listItemRemoved.observe(this.$el, {childList: true});
}
Perhaps you could use a custom directive. Perform the actions you need inside the unbind hook ..
created() {
this.vm = this
},
directives: {
foo: {
unbind(el, binding) {
// Here you can perform the actions you need.
// You can access the Vue instance using binding.value (eg: binding.value.$el)
}
}
},
And in your template ..
<transition-group class="message-bus" tag="ul" name="message-bus">
<li v-for="message in messages" v-bind:key="message.id" :ref="message.id" v-foo="vm">
<div>{{message.text}}</div>
<a class="dismiss-message" #click="dismissMessage(message)">×</a>
</li>
</transition-group>

VueJS 2 - Listen to event in mixin

I'm currently trying to create a mixin for Vue which basically creates a property passthrough chain. I'll clarify what should happen to be a little more clear;
Let's say I got 3 components; A,B and C.
A & B are both the same component called 'content-pane' (See below for template code).
<div class="pane-wrapper">
<div class="content-pane" :class="{'is-hidden' : !active}" :content="name">
<div class="card white">
<div class="card-title grey darken-3">
<h1 class="white-text">{{ label }}</h1>
</div>
<div class="card-content white">
<component
:is = "type"
:routes = "routes"
:passthrough = "passthrough"
keep-alive
></component>
</div>
</div>
</div>
<content-pane
v-for="(pane, key) in children"
:key = "key"
:label = "pane.label"
:name = "pane.name"
:active = "true"
:type = "pane.type"
:routes = "pane.routes"
></content-pane>
</div>
C is a dynamic component, meaning that it is interchangeable and could be any component.
Now I want to be able to access certain data from component C in component A, and for that I am trying to create a mixin that dyamically offers a data property to do this:
<script>
export default {
name: 'passthrough',
props: {
passthrough : {
type : Object
}
},
data ()
{
return {
// This object allows you to
// update the parent.
passthroughModifier : {
// We use the data object inside the
// original object because Vue doesn't
// want to detect direct prop changes
// when they are added dynamically
// into the root object...
data : {}
}
}
},
methods : {
/**
* This function fires an emit event.
*/
emitUpdate ()
{
this.$emit('passthrough-update', this.passthroughModifier);
}
},
watch : {
/**
* Emit an event once the passthrough
* property has been changed.
* We need to use a deep watcher.
*/
'passthroughModifier' : {
handler : function (val) {
this.emitUpdate();
},
deep: true
}
},
created ()
{
// Allow access to the instance
// inside the iteration.
let _that = this;
// Attach a listener for the passthrough update
// which will walk through all the keys in the
// data object and hard-set these locally.
this.$on('passthrough-update', function (data) {
Object.keys(data).forEach(function (index) {
_that.passthroughModifier[index] = data[index];
});
});
}
}
Everything works fine except listening to the 'passthrough-update' event, which is fired by the watcher on $.passthroughModifier.
So; When component C updates its $.passthroughModifier.data, the event gets emitted, but component B isn't able to catch this event.
I have tried to listen for this event in the created() method of the mixin (see code above), but it seems as if the event only gets caught in the component the event is fired from. So component C fires the event, and component C listens to its own event.
I hope someone is able to tell me wether this is actually possible or not, and what I'm doing wrong if it is possible.

How to trigger event in all sibling components except current component in vuejs?

I have a reusable component that does inline editing for data.
So a page has 10 fields that can be edited inline,
<editfield :value="field" v-for="field in fieldslist"></editfield>
Each of them have a data field called "editing" that sets as true or false as the user clicks on it. Everytime a field is set to editing an event editing-another-field is emitted using event bus.
edit(){
this.editing = true;
EventBus.$emit('editing-another-field');
}
I added the event listener when the component is created
created(){
EventBus.$on('editing-another-field', ()=>{ this.editing = false;});
}
The problem I am facing is it is triggering the event even in the currennt component being edited.
How can I mention that updated value of editing in all the other sibling components except the current component.
Why not pass the current component as an event argument and use that to check if the event originated from this component or another one.
edit() {
this.editing = true;
EventBus.$emit('editing-another-field', this);
}
created() {
EventBus.$on('editing-another-field', source => {
if (source !== this) {
this.editing = false;
}
});
}
Or you can do it like this (it is important to unregister the event listener when the component is destroyed to avoid a memory leak):
edit() {
EventBus.$emit('editing-field', this);
}
created() {
this.editingFieldHandler = vm => {
this.editing = vm === this;
};
EventBus.$on('editing-field', this.editingFieldHandler);
}
destroyed() {
EventBus.$off('editing-field', this.editingFieldHandler);
}
Otherwise you can emit the event first and then set this.editing to true.
Are you sure you want an event bus? This brings up bad memories of JQuery ;-) I think it would be cleaner to limit yourself to a tree of parents and children. Thinking MVVM, formLockedBy is a perfectly valid and sensible property to store on the parent and pass to the children.
The solution below, running here, shows a form with two fields. The fields are both instances of modal-component. The parent manages the formLockedBy property. The child fields look at this property to know to disable themselves. When the user starts typing in a field, the field emits an editing event and formLockedBy gets set. Similarly, when a field emits a save or cancel event, the parent clears formLockedBy and the other input(s) spring back to life.
Note the advantages...
Only the parent listens for events.
The identifier stored in formLockedBy is just the string name of the field. This is much safer than passing and storing a reference to the Vue component. If you don't like this, you might consider adding a safe id to the object proto.
No surprises. The full list of events the parent will react to is declared in the tag instantiating the child. The child specifies in props everything it needs from the parent.
HTML
<div id="example">
<modal-input name='First Name'
:form-locked-by='this.formLockedBy'
v-on:save='formLockedBy = null'
v-on:cancel='formLockedBy = null'
v-on:editing='fieldActive'
></modal-input>
<modal-input name='Address'
:form-locked-by='this.formLockedBy'
v-on:save='formLockedBy = null'
v-on:cancel='formLockedBy = null'
v-on:editing='fieldActive'
></modal-input>
</div>
JS
Vue.component('modal-input', {
template: `<div>
{{name}} :
<input :name='name' type="text" v-on:keydown="active" :disabled="formLockedBy && formLockedBy != name"/>
<span v-if="editing && formLockedBy == name">
<input type="button" value="Save" v-on:click="$emit('save');editing=false;"></input>
<input type="button" value="Cancel" v-on:click="$emit('cancel');editing=false;"></input>
</span>
</div>`,
data : function(){
return {editing:false};
},
props: ['name','formLockedBy'],
methods : {
active : function(event){
if(!this.editing){
this.editing = true;
this.$emit('editing',{field:this.name})
}
return true;
}
}
});
// create a root instance
new Vue({
el: '#example',
data: {
formLockedBy : null
},
methods : {
fieldActive : function(args){
this.formLockedBy = args.field;
}
}
})

Using vue.js transition tags in Shopify liquid templates

It appears that Shopify is removing the transition tag:
<transition name="fade"> ... </transition>
In addition, when I wrap my v-for loop in the transition it only renders the first one then stops. No errors in the console or Vue inspector.
Are there any possible work arounds for animating without these or getting them to parse in liquid?
To clarify I need to transition a sortable group of products that I'm using the filter method on to toggle.
v-for="product in filteredProducts"
Then my filter:
filteredProducts: function() {
var parent = this;
return parent.products.filter(function (product) {
if(parent.selected.length == 0) {
return product;
} else {
console.log(product.id);
for (var i = parent.selected.length - 1; i >= 0; i--) {
if(parent.selected[i].products.includes(product.id)) {
return product;
}
}
}
});
},
It turns out the issue wasn't with Shopify at all, but rather the v-bind:key attribute missing on v-for in order to work with a transition group.
<div v-for="product in filteredProducts" v-if="product.available" v-bind:key="product">
Prior to that everything nested in the transition-group was being removed, and would only work for the first item.