36

I am trying to build a base DataComponent which will contain the common functionality required for many other components which deal with basic CRUD entities.
So far I have

//main.js
import Vue from 'vue';

new Vue({
  el:'#app',
  components:{
    DataComponent,
    Quotation
  }
});

//data-component.js
import Vue from 'vue';

export
default Vue.extend({

      data() {
          return {
            saved: false
          }
        },

        methods: {

          //This method will be used by all inheriting components to alert
          //the user regarding any changes which need to be saved.
          
          alertSave(entity, fields, intFields) {
              var changeCount = 0;
              fields.forEach(function(field) {
                
                var compareWith = this[field];
                
                //Here "this" need to refer to the child instance but it does not
                //how can I achieve?
                
                if ((compareWith || entity[field.camelToSnake()]) &&
                  entity[field.camelToSnake()] !== compareWith) {
                  changeCount++;
                }
              });
              intFields.forEach(function(field) {
                var compareWith = parseInt(this[field]);
                if ((compareWith || entity[field.camelToSnake()]) &&
                  entity[field.camelToSnake()] !== compareWith) {
                  changeCount++;
                }
              });
              vm.saved = changeCount <= 0;
            },
          
            //sanitizeValue method works as intended as it does not have any reference to "this"
          
            sanitizeValue(value) {
              if (value) {
                return String(value).trim();
              } else {
                return null;
              }
            },
          
            //In getDbData method also this needs to refer to the inheriting child instance
            //from where this method is called - how can I achieve it?
          
            getDbData(entity) {
              if (entity) {
                this.dbTextFields.forEach(function(field) {
                  this[field] = entity[field.camelToSnake()];
                });
                this.dbIntFields.forEach(function(field) {
                  this[field] = entity[field.camelToSnake()];
                });
                this.dbObjFields.forEach(function(field) {
                  this[field] = entity[field.camelToSnake()];
                });
                this.dbAppendedFields.forEach(function(field) {
                  this[field] = entity[field.camelToSnake()]
                });
                this.saved = true;

              }
            }
        });

//quotation.js

import DataComponent from './data-component';

export default DataComponent.extend({
  
  data(){
    return{
      id:0,
      date:'',
      remarks:'',
      terms:'',
      quote:{},
      dbTextFields:['to', 'org', 'address', 'items', 'description', 'quoted_by'],
      dbIntFields:['quote_ref', 'quantity', 'amount', 'discount', 'total'],
      dbObjFields:['inquiry', 'booking']
    }
  },
  
  methods:{
    setDbData(){
      let entity = this.quote;
      this.getDbData(entity);
      
      //getDbData gives error as "this" in getDbData does not refer to this
      // child component and so this.dbTextFields becomes undefined.
      
    }
  }
  
});

How to achieve method inheritance as I am trying to do? Is it possible in Vue.js?  

Edit

If I change the signature of the method in data-component.js as under, passing the instance of inheriting component ("this") as vm , it works

//data-component.js
import Vue from 'vue';

export
default Vue.extend({

      data() {
          return {
            saved: false
          }
        },

        methods: {

          //This method will be used by all inheriting components to alert
          //the user regarding any changes which need to be saved.
          
          alertSave(entity, fields, intFields, vm) {
              var changeCount = 0;
              fields.forEach(function(field) {
                //var compareWith = this[field];
                var compareWith = vm[field];
                
                //Changed "this" to vm (passed as a parameter) 
                //how can I achieve?
                
                if ((compareWith || entity[field.camelToSnake()]) &&
                  entity[field.camelToSnake()] !== compareWith) {
                  changeCount++;
                }
              });
              intFields.forEach(function(field) {
                //var compareWith = parseInt(this[field]);
                var compareWith = parseInt(vm[field]);
                if ((compareWith || entity[field.camelToSnake()]) &&
                  entity[field.camelToSnake()] !== compareWith) {
                  changeCount++;
                }
              });
              vm.saved = changeCount <= 0;
            },
          
            //sanitizeValue method works as intended as it does not have any reference to "this"
          
            sanitizeValue(value) {
              if (value) {
                return String(value).trim();
              } else {
                return null;
              }
            },
          
            //In getDbData method also this needs to refer to the inheriting child instance
            //from where this method is called - how can I achieve it?
          
            getDbData(entity, vm) { //instance as "vm" parameter
            //change all this to vm
              if (entity) {
                vm.dbTextFields.forEach(function(field) {
                  vm[field] = entity[field.camelToSnake()];
                });
                vm.dbIntFields.forEach(function(field) {
                  vm[field] = entity[field.camelToSnake()];
                });
                vm.dbObjFields.forEach(function(field) {
                  vm[field] = entity[field.camelToSnake()];
                });
                vm.dbAppendedFields.forEach(function(field) {
                  vm[field] = entity[field.camelToSnake()]
                });
                vm.saved = true;

              }
            }
        });

And then in the inheriting component

//quotation.js

import DataComponent from './data-component';

export default DataComponent.extend({
  
  data(){
    return{
      id:0,
      date:'',
      remarks:'',
      terms:'',
      quote:{},
      dbTextFields:['to', 'org', 'address', 'items', 'description', 'quoted_by'],
      dbIntFields:['quote_ref', 'quantity', 'amount', 'discount', 'total'],
      dbObjFields:['inquiry', 'booking']
    }
  },
  
  methods:{
    setDbData(){
      let entity = this.quote;
      this.getDbData(entity, this);
      
      //passing this (instance) as a parameter
      
      
    }
  }
  
});

Passing the instance ("this") to the methods as vm, it works as expected.

I am not sure if this is the best way to do it. But then it surely is not inheritance.
How to use inheritance to achieve what I am trying to do?

1 Answer 1

31

You should use Mixins to add common functionality to multiple (or all of your) components: https://v2.vuejs.org/v2/guide/mixins.html

This will let you add the same functions to any or all of your components, so you can automatically add this.foobar() to your components.

If you want to add functionality to all of your components without polluting your component namespace, you can use a custom plugin: https://vuejs.org/guide/plugins.html

This will let you add a service to all of your components so you can use it everywhere like this.$service.foobar().

If you want to work with CRUD functionality, you should create a resource using VueResource: https://github.com/vuejs/vue-resource/blob/master/docs/resource.md

This will let you easily create/delete/edit resources using FoobarService.create({foo: 'bar'}) or FoobarService.delete({id: 1})

Edit: to create a plugin, it will look something like this:

var MyPlugin = {};

MyPlugin.install = function (Vue, options) {

  var service = {
    foo: function(bar) {
      //do something
    }
  }

  Vue.prototype.$service = service;
}

Vue.use(MyPlugin); //this runs the install function

//Then when you have any Vue instance
this.$service.foo(bar);
Sign up to request clarification or add additional context in comments.

7 Comments

Can you please elaborate a little on how can I approach to write a plugin for my requirement? I cannot figure out from the information on the plugin link. The example shown provides three options global methods, directives and instance method. I am unable to figure out where should I put my methods in the plugin definition so that I can then use it in components as this.$service.method(). Can you point me to some resource where I can find detailed instructions or a "Hello World" kind of example on how to write a plugin? Please
Thank you very much @Jeff for taking interest and sparing time to help me and providing the plugin code. Thanks again.
I tried the mixin as well as the plugin approach. Using the mixin approach, I have to write import statement in each of the component (file) to use the mixin in the component and it allows using this in mixin methods which will refer to the calling component which I need. While when using the plugin approach, I don't have to import the plugin in each component (file), but it does not allow use of this in the methods to refer the calling component. So I have to pass the component instance reference vm=this and then pass vm as parameter to methods of plugin.
Well.. this is a common mistake to misunderstand "common functionality" with abstract classes - which is a concept from object-oriented programming. The relation being expressed with composition is "has a <component>". This is good for 80% of the cases. However if we have a set of controls that are the same just differ in small parts then it may be an "is a <component>" relation which is expressed with inheritance. A good example is a base dialog with imperative API (like a "showModal()" method) and dialogs that are inherited from that base dialog component.
The suggested edit queue is full, but here's the new link to the mixin documentation guide
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.