Categories
JavaScript Vue

Introduction to Vue.js Directives

Vue.js is an easy to use web app framework that we can use to develop interactive front end apps.

In this article, we’ll look at how to manipulate the DOM with Vue.js directives.

Basics

Vue lets us register or own custom directives, in addition to using built-in ones like v-for and v-if .

It’s useful for low-level DOM manipulation that can’t easily be done with components.

For example, we can make one as follows:

src/index.js :

Vue.directive("focus", {  
  inserted(el) {  
    el.focus();  
  }  
});
const vm = new Vue({  
  el: "#app"  
});

index.html :

<!DOCTYPE html>  
<html>  
  <head>  
    <title>App</title>  
    <meta charset="UTF-8" />  
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>  
  </head>  
  <body>  
    <div id="app">  
      <input v-focus />  
    </div>  
    <script src="src/index.js"></script>  
  </body>  
</html>

In the code above, we defined the focus directive, which is referenced by adding a v- prefix to it.

The focus directive calls focus on the DOM element when it’s inserted into the DOM. The inserted method is a hook that handles the situation when the element that has the directive applied is inserted into the DOM.

The el parameter has the DOM element, which is why we can call the focus method on it.

Therefore, when we load the page, we’ll see that the input element is focused. This is the work of the focus directive.

The code above registered the directive globally. We can also register the directive locally as follows:

src/index.js :

const focus = {  
  inserted(el) {  
    el.focus();  
  }  
};

const vm = new Vue({  
  el: "#app",  
  directives: {  
    focus  
  }  
});

index.html :

<!DOCTYPE html>  
<html>  
  <head>  
    <title>App</title>  
    <meta charset="UTF-8" />  
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>  
  </head>  
  <body>  
    <div id="app">  
      <input v-focus />  
    </div>  
    <script src="src/index.js"></script>  
  </body>  
</html>

The focus directive will then only be available to the component that it’s registered in.

Hook Functions

A directive can have the following hook functions. They’re all optional.

  • bind — called only once when the directive is first bound to the element. It’s used for one-time setup work
  • inserted — called when the bound element is inserted into its parent node. It guarantees the parent node presence.
  • updarted — calls after the containing component VNode has updated but possibly before children have updated. VNode is a node in the virtual DOM, it stands for virtual node.
  • componentUpdated — called when the containing component’s VNode and VNodes of its children are updated
  • unbind — called only once when the directive is unbound from the element

Directive Hook Arguments

The hooks above take the following arguments:

el

The element the directive is bound to. It can used to manipulate the DOM

binding

An object containing the following properties:

  • name — name of the directive without the v- prefix
  • value — value passed to the directive. For example, if we have v-foo='1' then the value is 1 .
  • oldValue — the previous value. This is only available in update and compoentUpdated . It’s available whether or not the value has changed.
  • expression — the expression of the binding as a string. For example, if we have v-foo='1 + 2' then it’s 1 + 2 .
  • arg —the argument passed into the directive. For example, if we have v-foo:bar then the arg is ‘bar‘.
  • modifiers — an object with modifiers. If we have v-foo.bar.baz then it would be { bae: true, baz: true } .

Everything other than el are read-only.

For example, we can display the data that are passed from the parameters as follows:

src/index.js :

const foo = {  
  inserted(el, binding, vnode) {  
    const stringify = JSON.stringify;  
    el.innerHTML = `  
      name:   
        ${stringify(binding.name)}  
      <br>  
      value:    
       ${stringify(binding.value)}  
      <br>  
      expression:  
        ${stringify(binding.expression)}  
      <br>  
      argument  
       ${stringify(binding.arg)}  
      <br>  
      modifiers:  
        ${stringify(binding.modifiers)}  
      <br>  
      vnode keys:  
       ${Object.keys(vnode).join(", ")}`;  
  }  
};

const vm = new Vue({  
  el: "#app",  
  directives: {  
    foo  
  }  
});

index.html :

<!DOCTYPE html>  
<html>  
  <head>  
    <title>App</title>  
    <meta charset="UTF-8" />  
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>  
  </head>  
  <body>  
    <div id="app">  
      <div v-foo:bar.baz="1 + 2"></div>  
    </div>  
    <script src="src/index.js"></script>  
  </body>  
</html>

Then we get:

name: "foo"  
value: 3  
expression: "1 + 2"  
argument "bar"  
modifiers: {"baz":true}  
vnode keys: tag, data, children, text, elm, ns, context, fnContext, fnOptions, fnScopeId, key, componentOptions, componentInstance, parent, raw, isStatic, isRootInsert, isComment, isCloned, isOnce, asyncFactory, asyncMeta, isAsyncPlaceholder

Conclusion

We can define our own Vue directives to manipulate DOM elements by binding them to specific DOM elements and then run the elements’ methods.

It works by running hooks when certain during certain stages of virtual node manipulation by Vue.

The hooks take the DOM element object as the first argument and a binding object which has the data about the directive like name, the value passed in, modifiers and arguments.

Categories
JavaScript Vue

How to Create Vue Directive

Vue.js is an easy to use web app framework that we can use to develop interactive front end apps.

In this article, we’ll look at how to make our directives accept arguments and values to directives and use them in our code.

Dynamic Directive Arguments

We can pass in arguments and values to a directive and then get these values from the binding parameter.

For example, we can make a directive that takes an argument and value as follows:

src/index.js :

Vue.directive("position", {  
  bind(el, binding, vnode) {  
    const validPositions = ["relative", "fixed", "absolute"];  
    if (validPositions.includes(binding.arg)) {  
      el.style.position = binding.arg;  
      el.style.top = `${binding.value}px`;  
    }  
  }  
});

new Vue({  
  el: "#app",  
  data: {  
    message: "Hello"  
  }  
});

index.html :

<!DOCTYPE html>  
<html>  
  <head>  
    <title>App</title>  
    <meta charset="UTF-8" />  
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>  
  </head>  
  <body>  
    <div id="app">  
      <p v-position:absolute="50">{{message}}</p>  
    </div>  
    <script src="src/index.js"></script>  
  </body>  
</html>

In the code above, we defined a positiion directive that takes an argument, which is accessed in the bind hook by using the binding parameter.

The binding parameter has an arg property to get the argument, which is absolute in index.html .

binding.value gets us the value, which is 50 in index.html .

Then we get the element that the directive is bound to with el and then we set the positioning of the element via the values from the binding parameter.

Accepting arguments and values make directives much more flexible than without arguments.

Function Shorthand

If we only have code in the bind and update hooks, then we can just pass in a function with the same signature as those hooks.

For example, we can shorten the example above to:

Vue.directive("position", (el, binding, vnode) => {  
  const validPositions = ["relative", "fixed", "absolute"];  
  if (validPositions.includes(binding.arg)) {  
    el.style.position = binding.arg;  
    el.style.top = `${binding.value}px`;  
  }  
});

They both do the same thing.

Object Literals

We can pass in an object literal as the value of a directive if our directive needs to accept multiple values.

For example, we can write the following code to accept an object’s properties’ values in our directive:

src/index.js:

Vue.directive("position", (el, binding, vnode) => {  
  const validPositions = ["relative", "fixed", "absolute"];  
  const { position, top } = binding.value;  
  if (validPositions.includes(position)) {  
    el.style.position = position;  
    el.style.top = top;  
  }  
});

new Vue({  
  el: "#app",  
  data: {  
    message: "Hello"  
  }  
});

index.html :

<!DOCTYPE html>  
<html>  
  <head>  
    <title>App</title>  
    <meta charset="UTF-8" />  
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>  
  </head>  
  <body>  
    <div id="app">  
      <p v-position="{ position: 'absolute', top: '50px' }">{{message}}</p>  
    </div>  
    <script src="src/index.js"></script>  
  </body>  
</html>

The code above accepts an object with the position and top property and we take the object’s values and then set them as the style of the p element which the position directive is bound to.

A directive’s value can be any valid JavaScript expression.

Conclusion

Vue directives can take arguments and values. We can get argument values from the binding parameter of the bind hook via the binding.arg property.

To get the value passed to a directive, we can get it from the bind hook’s binding parameter via the binding.value property.

If our directive only has bind or update hooks, then we can shorten it to a function, which has the same signature as the bind or update hooks.

Categories
JavaScript Vue

Vue Components — Dynamic Slot Names and Shorthands

Vue.js is an easy to use web app framework that we can use to develop interactive front end apps.

In this article, we’ll look at add dynamic slot names.

Dynamic Slot Names

Since Vue.js 2.6.0, we can pass in dynamic slot names with v-slot . For example, we can use it as follows:

src/index.js :

Vue.component("user", {  
  data() {  
    return {  
      user: {  
        firstName: "Joe",  
        lastName: "Smith"  
      }  
    };  
  },  
  template: `<p>  
    <slot v-bind:user="user" name='first-name'></slot>  
    <slot v-bind:user="user" name='last-name'></slot>  
  </p>`  
});

new Vue({  
  el: "#app",  
  data: {  
    firstname: "first-name",  
    lastname: "last-name"  
  }  
});

index.html :

<!DOCTYPE html>  
<html>  
  <head>  
    <title>App</title>  
    <meta charset="UTF-8" />  
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>  
  </head>  
  <body>  
    <div id="app">  
      <user>  
        <template v-slot:[firstname]="{ user }">  
          {{ user.firstName }}  
        </template>  
        <template v-slot:[lastname]="{ user }">  
          {{ user.lastName }}  
        </template>  
      </user>  
    </div>  
    <script src="src/index.js"></script>  
  </body>  
</html>

In the code above, firstname and lastname in data are set as the names of the slots in our root template.

Named Slots Shorthand

v-slot has a shorthand. We can shorten v-slot: to the symbol # . For example, we can rewrite our example as follows:

src/index.js :

Vue.component("user", {  
  data() {  
    return {  
      user: {  
        firstName: "Joe",  
        lastName: "Smith"  
      }  
    };  
  },  
  template: `<p>  
    <slot v-bind:user="user" name='first-name'></slot>  
    <slot v-bind:user="user" name='last-name'></slot>  
  </p>`  
});

new Vue({  
  el: "#app"  
});

index.html :

<!DOCTYPE html>  
<html>  
  <head>  
    <title>App</title>  
    <meta charset="UTF-8" />  
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>  
  </head>  
  <body>  
    <div id="app">  
      <user>  
        <template #first-name="{ user }">  
          {{ user.firstName }}  
        </template>  
        <template #last-name="{ user }">  
          {{ user.lastName }}  
        </template>  
      </user>  
    </div>  
    <script src="src/index.js"></script>  
  </body>  
</html>

For referencing the default slot, we have to write #default . For example, we write:

src/index.js :

Vue.component("user", {  
  data() {  
    return {  
      user: {  
        firstName: "Joe",  
        lastName: "Smith"  
      }  
    };  
  },  
  template: `<p>  
    <slot v-bind:user="user"></slot>      
  </p>`  
});

new Vue({  
  el: "#app"  
});

index.html :

<!DOCTYPE html>  
<html>  
  <head>  
    <title>App</title>  
    <meta charset="UTF-8" />  
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>  
  </head>  
  <body>  
    <div id="app">  
      <user>  
        <template #default="{ user }">  
          {{ user.firstName }} {{ user.lastName }}  
        </template>  
      </user>  
    </div>  
    <script src="src/index.js"></script>  
  </body>  
</html>

Use with v-for

We can add a slot to provided fallback content in lists and allow us to change the content in the parent. For example, we can write the following:

src/index.js :

Vue.component("people", {  
  data() {  
    return {  
      persons: [  
        { name: "Joe", isAdult: true },  
        { name: "Jane", isAdult: false }  
      ]  
    };  
  },  
  template: `  
    <ul>    
      <li v-for='person of persons'>  
        <slot v-bind:person="person"></slot>      
      </li>  
    </ul>  
  `  
});

new Vue({  
  el: "#app"  
});

index.html :

<!DOCTYPE html>  
<html>  
  <head>  
    <title>App</title>  
    <meta charset="UTF-8" />  
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>  
  </head>  
  <body>  
    <div id="app">  
      <people v-bind:persons="persons">  
        <template #default="{ person }">  
          {{person.name}}  
          <span v-if="person.isAdult"> - Adult</span>  
          <span v-else> - Child</span>  
        </template>  
      </people>  
    </div>  
    <script src="src/index.js"></script>  
  </body>  
</html>

In the code above, we have the people component with persons data. We rendered persons with v-for in the people component and make the entries available via v-bind :

<slot v-bind:person="person"></slot>

Then in the root template, we have:

<people v-bind:persons="persons">  
  <template #default="{ person }">  
    {{person.name}}  
    <span v-if="person.isAdult"> - Adult</span>  
    <span v-else> - Child</span>  
  </template>  
</people>

to get person from people and then provide a template to display the entries as we wish without modifying the people component.

Conclusion

Since Vue 2.6.0, we can use dynamic slot names instead of hard-coded ones.

Also, the shorthand for named slots is # . For default slots, we always have to write #default .

Finally, we can use it with v-for to let us define a template that can be used with each entry. We use v-bind on the entry of the array instead of the whole array and then we can access that data in the parent component and add a templarte to display data the way we like by defining it in the parent.

Categories
JavaScript Vue

Introduction to Vue.js Event Handling — More Modifiers

Vue.js is an easy to use web app framework that we can use to develop interactive front end apps.

In this article, we’ll look at how to handle various events with Vue.js and modify an event handler’s behavior.

Key Modifiers

When listening to keyboard events, we often want to check for specific keys. To do this, we can add the key name as the modifier of the v-on directive.

For example, we can use it as follows:

src/index.js :

new Vue({  
  el: "#app",  
  data: {  
    name: ""  
  },  
  methods: {  
    submit() {  
      alert(this.name);  
    }  
  }  
});

index.html :

<!DOCTYPE html>  
<html>  
  <head>  
    <title>App</title>  
    <meta charset="UTF-8" />  
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>  
  </head> <body>  
    <div id="app">  
      <input v-model="name" v-on:keyup.enter="submit" />  
    </div>  
    <script src="src/index.js"></script>  
  </body>  
</html>

The code above will watch for presses for the enter key when the input if focused. Then when we press Enter and release the key at that time, we’ll get whatever’s typed into the input displayed in the alert box.

v-on:keyup.enter=”submit” only checks for when the Enter key is released.

For key names that are more than one word, we convert it to kebab case.

For example, if we want to listen to the keyup event of the page up key, we write:

<input v-on:keyup.page-up="onPageUp">

Then when the page up key is released, onPageUp is called.

Key Codes

We can also pass the key code as the modifier of v-on . For example, if we want to listen to the event when the Enter key is released after it’s pressed, we can write the following:

src/index.js :

new Vue({  
  el: "#app",  
  data: {  
    name: ""  
  },  
  methods: {  
    submit() {  
      alert(this.name);  
    }  
  }  
});

index.html :

<!DOCTYPE html>  
<html>  
  <head>  
    <title>App</title>  
    <meta charset="UTF-8" />  
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>  
  </head> <body>  
    <div id="app">  
      <input v-model="name" v-on:keyup.13="submit" />  
    </div>  
    <script src="src/index.js"></script>  
  </body>  
</html>

However, the use ofkeyCode events are deprecated so it may not be supported in new browsers.

Aliases for commonly used key codes are included in Vue. They include:

  • .enter
  • .tab
  • .delete (captures both “Delete” and “Backspace” keys)
  • .esc
  • .space
  • .up
  • .down
  • .left
  • .right

Some keys, like arrow keys and .esc have inconsistent values in IE9, so these built-in aliases should be preferred if our app supported IE9.

We can also define our own key code aliases as follows:

Vue.config.keyCodes.f3 = 114

Then we can use:

v-on:keyup.f3

in our app.

System Modifier Keys

Since Vue 2.1.0 or later, we can use modifier keys to trigger mouse or keyboard event listeners only when the corresponding modifier keys are pressed:

  • .ctrl
  • .alt
  • .shift
  • .meta

The meta key is the command key on Macintosh keyboards. On Windows keyboards, the meta key is the windows key. On Sun workstation keyboards, it’s solid diamond key.

For example, if we want Alt-S to be the shortcut key to display an alert with what we typed, we can write the following:

src/index.js :

new Vue({  
  el: "#app",  
  data: {  
    name: ""  
  },  
  methods: {  
    submit() {  
      alert(this.name);  
    }  
  }  
});

index.html :

<!DOCTYPE html>  
<html>  
  <head>  
    <title>App</title>  
    <meta charset="UTF-8" />  
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>  
  </head> <body>  
    <div id="app">  
      <input v-model="name" @keyup.alt.83="submit" />  
    </div>  
    <script src="src/index.js"></script>  
  </body>  
</html>

If we want an alert to pop up when we Ctrl-click on a button, we can write the following:

src/index.js :

new Vue({  
  el: "#app",  
  data: {},  
  methods: {  
    sayHi() {  
      alert("hi");  
    }  
  }  
});

index.html :

<!DOCTYPE html>  
<html>  
  <head>  
    <title>App</title>  
    <meta charset="UTF-8" />  
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>  
  </head> <body>  
    <div id="app">  
      <button @click.ctrl="sayHi">Say Hi</button>  
    </div>  
    <script src="src/index.js"></script>  
  </body>  
</html>

Then we get an alert with the word ‘hi’ when we Ctrl-click on the Say Hi button.

The modifier keys have to pressed when the event is emitted in both cases. So in the examples above, Alt or Ctrl have to press in addition to what comes after it.

.exact Modifier

Since Vue 2.5.0, we can use the .exact modifier to control the exact combination of system modifiers needed to trigger an event.

For example:

<button @click.ctrl="onClick">A</button>

will run onClick only when Ctrl is pressed, and if we want to run a method only when Ctrl-click and no other keys are pressed, we can write:

<button @click.ctrl.exact="onCtrlClick">A</button>

Mouse Button Modifiers

Since Vue 2.2.0, the following mouse modifier events can be used:

  • .left
  • .right
  • .middle

They’ll triggered when a specific mouse button is pressed.

Conclusion

We can listen to one key action or a combination of key actions with key modifiers.

We can reference them by key code or aliases.

Key names that have more than one word have to be converted to kebab case.

We can also listen to exact key combinations with .exact and mouse button clicks with mouse key modifiers.

Categories
JavaScript Vue

Vue.js Mixins — Global Mixins and Custom Merge Strategies

Vue.js is an easy to use web app framework that we can use to develop interactive front end apps.

In this article, we’ll look at global mixins and custom merging strategies for merging mixins into components.

Global Mixin

We can create a mixin that’s applied globally. These mixins will affect every Vue instance created afterward. Therefore, we should be careful when using them.

We can use this to inject custom processing logic that applies to all components.

For example, we can define a global mixin as follows:

Vue.mixin({  
  created() {  
    const foo = this.$options.foo;  
    console.log(foo);  
  }  
});

new Vue({  
  el: "#app",  
  foo: "foo"  
});

Then we’ll see foo logged since the foo option is retrieved from this.$options.foo in the created hook.

When the created hook is called, we’ll see the console.log output since it’s called.

The above example shows how it should be used. Global mixins should be used infrequently since we want to minimize the chance of bugs created by conflicts from global mixins.

Custom Option Merge Strategies

We can adjust the strategies for merging mixin code into components.

To do this, we assign a function to Vue.config.optionMergeStrategies .

For example, we can write the following:

Vue.config.optionMergeStrategies.myOption = (toVal, fromVal) => {  
  return {  
    ...toVal,  
    ...fromVal  
  };  
};

Vue.mixin({  
  myOption: { foo: 1 }  
});

const vm = Vue.extend({  
  myOption: { foo: 2 }  
});

console.dir(vm.options.myOption);

We see that toVal is { foo: 1 } from the mixin and fromVal is { foo: 2 } from the component we created with Vue.extend .

Therefore, we merged myOption from the mixin into the component by letting the component’s myOption taking precedence.

This means that vm.options.myOption has the value { foo: 2 } .

We can write the same thing with new Vue as follows:

Vue.config.optionMergeStrategies.myOption = (toVal, fromVal) => {  
  return {  
    ...toVal,  
    ...fromVal  
  };  
};

const mixin = {  
  myOption: { foo: 1 }  
};

const vm = new Vue({  
  el: "#app",  
  myOption: { foo: 2 },  
  mixins: [mixin]  
});

console.dir(vm.$options.myOption);

We see that vm.options.myOption has the value { foo: 2 } again.

We can also flip them around, which means the mixin’s option has precedence over the component’s option if they have the same name by writing:

Vue.config.optionMergeStrategies.myOption = (toVal, fromVal) => {  
  return {  
    ...fromVal,  
    ...toVal  
  };  
};

const mixin = {  
  myOption: { foo: 1 }  
};

const vm = new Vue({  
  el: "#app",  
  myOption: { foo: 2 },  
  mixins: [mixin]  
});

console.dir(vm.$options.myOption);

Then we get { foo: 1 } from the console.log , which means the mixin ‘s myOption took precedence over the component’s myOption .

We can get various strategies for merging options by using Vue.config.optionMergeStrategies .

There are strategies for common option items like the lifecycle hook methods, watch, computed , methods , directives, etc.

Pretty much any option that we can put into Vue that aren’t our own custom options are in the Vue.config.optionMergeStrategies object.

We can use these preset strategies to set the merging strategies for our own options.

For example, if we want the methods strategy for merging myOption , we can write the following:

const strategies = Vue.config.optionMergeStrategies;  
strategies.myOption = strategies.methods;

Then the merging strategy for merging myOption will be the same as the strategy which we use for methods, which is the component’s methods have precedence over the mixin’s methods if they have the same name.

Conclusion

We can define global mixin to merge the option from the mixin to all components.

This shouldn’t be used frequently since it creates conflicts that are hard to trace.

It should only be used for our own custom-defined options.

We can also create our own mixin merging strategy by adding a property to Vue.config.optionMergeStrategies.optionName and setting a function that takes the option objects from the mixin and component respectively and returns the merged object.

Finally, we can access a full list of strategies by using the Vue.config.optionMergeStrategies object.