Vue - Extending Third Party Component templates

You found this library and it does exactly what you need however you want to change a minor detail.
There's 2 options that comes to mind here:

  1. 1 - You fork it and do whatever you need.
  2. 2 - Get over it. It's not a blocker and can't be bothered forking it.

In my previous article Vue - Overriding Third Party Defaults, we found out that default Props can be overridden. Just like props, template is just another key in a Vue component object.

Let us use vue-select as an example again.

In a previous project of mine, the client wanted a bit more of functionality from the vue-select. They wanted vue-select to:

  • have slide animation
  • go upwards if not within viewport.

The problem is although vue-select exposes a "transition" prop, it does not do what is needed. You can probably fake the sliding down by setting CSS max-height from 0 to 999px but it does not solve the going upwards problem.

There are many ways to solve this problem, one of them being the max-height trick, and for the going upwards, maybe just add some javascript on the containing component.

I decided to fix this in the root and also because I wanted to use VelocityJS for the sliding. So here's how I did it:

import VueSelect from 'vue-select'
import Velocity from 'velocity-animate'

const originalData = VueSelect.data
const originalRender = VueSelect.render
const originStateClasses = VueSelect.computed.stateClasses

VueSelect.computed.stateClasses = function() {
  return {
    'vs--upwards': this.upwards,
    ...originalStateClasses.call(this)
  }
}

VueSelect.render = function(h) {
  const rendered =  originalRender.call(this, h)

  const transitionComponent = rendered.children.find(c => {
    return c.componentOptions && c.componentOptions.tag === 'transition'
  })

  transitionComponent.componentOptions.propsData = {
    css: false
  }

  transitionComponent.componentOptions.listeners = {
    enter: (el, done) => {
      Velocity(el, "slideDown", {
        duration: 300,
        complete: done,
        begin: () => {
          const rect = el.getBoundingClientRect();
          const withinViewPort = rect.top + rect.height < window.innerHeight
          this.upwards = !withinViewPort
        }
      })
    },

    leave: (el, done) => {
      Velocity(el, "slideUp", {
        duration: 300,
        complete: done
      })
    },

    afterLeave: (el) => {
      this.upwards = false
    }
  }

  return rendered
}

First, I copied all the original properties that we're going to extend because otherwise we'll have to completely rewrite the same thing again, apart from the new stuff.

Next, I added another CSS class called vs--upwards which I'm going to style when the dropdown goes upwards, and added all the original computed classes by appending the result of the original computed function.

We have to call the original function by doing originalFn.call(this) instead of originalFn() because we need to pass the context or "this".

Next, on the render function, we store the return value of the original render which would be a VNode. Read more.

Now we have the VNode that contains the original structure of the component or the part of a Vue SFC.

Going back to the problem, we know that using CSS transition is not going to work. According to Vue's transition docs, we can take advantage of the JavaScript hooks where we can use VelocityJS.
And from there, we just add our Sliding logic and if it's going to be upwards or not, and return the new VNode.

You didn't have to fork, and you got what you wanted in a couple lines of codes. Win-Win!

See the Pen Vue - Extending third-party component template by Lemuel Flores (@captainskippah) on CodePen.