A directive to govern them all
Published: 2022-10-31 17:27:52

### What’s a directive? 🧏🏾‍♂️

A custom directive is defined as an object containing lifecycle hooks similar to those of a component. The hooks receive the element the directive is bound to. Here is an example of a directive that focuses an input when the element is inserted into the DOM by Vue:

```html
<script setup>
// enables v-focus in templates
const vFocus = {
  mounted: (el) => el.focus()
}
</script>

<template>
  <input v-focus />
</template>
```

In `<script setup>`, any camelCase variable that starts with the `v` prefix can be used as a custom directive. In the example above, `vFocus` can be used in the template as `v-focus`.

If not using `<script setup>`, custom directives can be registered using the `directives` option:

```jsx
export default {
  setup() {
    /*...*/
  },
  directives: {
    // enables v-focus in template
    focus: {
      /* ... */
    }
  }
}
```

It is also common to globally register custom directives at the app level:

```jsx
const app = createApp({})

// make v-focus usable in all components
app.directive('focus', {
  /* ... */
})
```

Next, we will do this by defining a plugin

### How can we implement a plugin ? 💁🏾‍♂️

A plugin is defined as either an object that exposes an `install()`
 method, or simply a function that acts as the install function itself. The install function receives the **[app instance](https://vuejs.org/api/application.html)** along with additional options passed to `app.use()`, if any:

```jsx
const vueGates = {
  install(app, options) {
    // configure the app
  }
}
```

Plugins are self-contained code that usually add app-level functionality to Vue. This is how we install a plugin:

```jsx
import { createApp } from 'vue'

const app = createApp({})

app.use(vueGates, {
  /* optional options */
})
```

### Writing my Plugin  🔌

Let's begin by setting up the plugin object. It is recommended to create it in a separate file and export it, as shown below to keep the logic contained and separate.

```jsx
// plugins/vueGates.js
export default {
  install: (app, options) => {
    // Plugin code goes here
  }
}
```

We want to create a Gate class. This Gate can allows a user in relation to a certain door (it’s like a function). It’s will work like that.

 

```jsx
<template v-gate:can="addAUser">
	<h1> You are able to create a new user </h1>
</template>

<template v-gate:allows="clickButton">
	<button> You can click it cause you're V.I.P </button>
</template>

<template v-gate:authorize="toSeeTheDoc">
	<doc
		:name="doc.name"
		:author="doc.author"
		:src="doc.src" 
	/> 
</template>
```

Or like that

```jsx
<template v-if="$gate.can('addAUser')">
	<h1> You are able to create a new user </h1>
</template>

<template v-if="$gate.allows('clickButton')">
	<button> You can click it cause you're V.I.P </button>
</template>

<template v-if="$gate.authorize('toSeeTheDoc')">
	<doc
		:name="doc.name"
		:author="doc.author"
		:src="doc.src" 
	/> 
</template>
```

At work

First we will create a new vuejs application. I use the vue-cli by typing

```bash
npm i vue-cli
vue create VueGate
```

Create a folder called plugins in the src folder. In this folder, we will create a `gate.js` file that will contain the Gate class17 octobre 2022 01:00 

```jsx
//src/plugins/VueGate/gate.js
class Gate {
  constructor(user) {
    this.user = user;
    this.policies = {};
    this.abilities = {};
    this.resolveUserFn = () => null;
    this.resolveUserPermissionFn = () => [];
  }

  resolveUser(fn) {
    this.resolveUserFn = fn;
  }

  resolveUserPermission(fn) {
    this.resolveUserFn = fn;
  }

  registerPolicies(policies = {}) {
    Object.keys(policies).forEach((modelName) => {
      this.policies[modelName] = new policies[modelName]();
    });
  }

	registerAbilities(abilities) {
    Object.keys(abilities).forEach((name) => {
      this.define(name,abilities[name]);
    });
  }

  define(name, fn) {
    this.abilities[name] = fn;
  }

  allows(abilities = [], ...args) {
    if (typeof abilities === "string") {
      abilities = [abilities];
    }

    const user = this.resolveUserFn();

    if (!user) {
      return false;
    }

    return abilities.every((ability) => {
      if (this.abilities[ability]) {
        if (typeof this.abilities[ability] === "function") {
          return this.abilities[ability].call(null, user, ...args);
        }

        return false;
      }

      const model = args[0];

      if (!model) {
        return user.allPermissions.includes(ability);
      }

      const policy = this.policies[model.constructor.name];

      if (!policy) {
        return false;
      }

      if (typeof policy[ability] === "function") {
        return policy[ability].call(policy, user, ...args);
      }

      return false;
    });
  }

  denies(abilities = [], ...args) {
    return !this.allows(abilities, args);
  }

  can(permissions) {
    const allPermissions = this.resolveUserPermissionFn();
    if (allPermissions.length === 0) {
      console.warn(
        "You have to define the ways to get all permission of a user throught use resolveUserPermission method"
      );
    }
    return allPermissions.includes(permissions);
  }

  cannot(permissions) {
    return !this.can(permissions);
  }
}
const gate = new Gate();

export default gate;
```

Then we will create the plugin

```jsx
//src/plugins/VueGate/index.js
import gate from "./gate";

export default {
  install: (app, options) => {
		//options for gate's class
    gate.resolveUser(() => options.user || {});
    gate.registerPolicies(options.policies || {});
    gate.registerAbilities(options.abilities || {});

    app.directive("gate", {
      beforeMount: (el, binding,vnode) => {
        if (["can","cannot", "allows","denies","authorized", "unauthorized"].includes(binding.arg)) {
          if (!gate[binding.arg](binding.value)) {
            el.style.display = "none";
            vnode.el.remove();
          }
        }
      },
    });
  },
};
```

Our plugin thus created, we will use it

```jsx
//src/main.js
import { createApp } from "vue";
import App from "./App.vue";
import VueGates from "./plugins/vueGates";

createApp(App)
  .use(VueGates)
  .mount("#app");
```

As you saw in the `src/plugins/VueGate/index.js` file, the Gate class needs some options. Let’s create it

```jsx
//src/main.js
import { createApp } from "vue";
import App from "./App.vue";
import VueGates from "./plugins/vueGates";

const user = {
  id: 12062001,
  name: "Epoundor",
};
const policies= {}
const abilities = {};
createApp(App)
  .use(VueGates, {
      user,
      policies,
      abilities
  })
  .mount("#app");
```

Now you can use the `v-gate` directive everywhere in our app

17 octobre 2022 We gonna try it out . First of all define a new abilities

```jsx
//src/main.js

const abilities = {
  seeImage: (user) => {
    return typeof user.id === "string";
  },
};
```

Then in your component, use the `v-gate:allows=”’seeImage’”` directive

```jsx
<template>
  <img alt="Vue logo" src="./assets/logo.png" v-gate:allows="'seeImage'">
  <HelloWorld msg="Welcome to Your Vue.js App"/>
</template>
```

The Vue’s logo will disappear 

![Vue app without logo](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/2z5a35io5xrn1u6vw1i1.png)

Vue app without logo

We want pass it to a v-if or a v-show directive like

```jsx
<template>
  <img alt="Vue logo" src="./assets/logo.png" v-if="$gate.allows('seeImage')">
  <HelloWorld msg="Welcome to Your Vue.js App"/>
</template>
```

Let's modify our plugin a little by adding a line

```jsx
//src/plugins/VueGate/index.js

import gate from "./gate";

export default {
  install: (app, options) => {
    // // Plugin code goes here
    gate.resolveUser(() => options.user || {});
    gate.registerPolicies(options.policies || {});
    gate.registerAbilities(options.abilities || {});
		
		//We define $gate as global property
    app.config.globalProperties.$gate = gate;

    app.directive("gate", {
      beforeMount: (el, binding,vnode) => {
        if (["can","cannot", "allows","denies","authorized", "unauthorized"].includes(binding.arg)) {
          if (!gate[binding.arg](binding.value)) {
            el.style.display = "none";
            vnode.el.remove();
          }
        }
      },
    });
  },
};
```

### Conclusion 🫡

Now you’re able to create a directive and use it on a application. You will have noticed that in the Gate class I did not define some functions as authorized. I'll let you take care of that