0

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.

Masade
  • 715
  • 1
  • 11
  • 29

2 Answers2

4

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.

Decade Moon
  • 32,968
  • 8
  • 81
  • 101
  • Editing event first and then editing is what I already did. I was wondering is there any other way rather than passing "this" as an argument. May be refs or something. What you suggested works very well :) – Masade Jul 10 '17 at 09:52
  • I've edited with another way. I don't think you can really avoid passing an argument with the event though; you need to know which component emitted the event. Also don't forget to unregister the event listener (or `$destroy()` the event bus at some point) to avoid a memory leak. – Decade Moon Jul 10 '17 at 09:56
  • Yeah, true, we would `this` anyway. How can unregister the event here, I would need listening to the event always right? Right now there are 10 places where I listen to this, but can increase significantly may be 100. Please help me understand a bit more. – Masade Jul 10 '17 at 10:07
0

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;
    }
  } 
})
bbsimonbb
  • 27,056
  • 15
  • 80
  • 110
  • This works, however it does not make the component independent of the parent. It may look as non-reusable – Masade Jul 10 '17 at 17:35
  • I'm not sure I see the problem? Parent and child have a relationship, that's modelled and made explicit by the props and the $emit calls. They're independent in that they can be tested separately. If you want to make the props optional, you can, if it makes sense. But there's isolation. Children never get a reference to, or interact with, other children. – bbsimonbb Jul 10 '17 at 18:47