Masade
Masade

Reputation: 716

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.

Upvotes: 0

Views: 1171

Answers (2)

bbsimonbb
bbsimonbb

Reputation: 29020

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;
    }
  } 
})

Upvotes: 0

Decade Moon
Decade Moon

Reputation: 34306

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.

Upvotes: 4

Related Questions