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:
<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:
export default {
setup() {
/*...*/
},
directives: {
// enables v-focus in template
focus: {
/* ... */
}
}
}
It is also common to globally register custom directives at the app level:
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 along with additional options passed to app.use()
, if any:
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:
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.
// 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.
<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
<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
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
//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
//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
//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
//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
//src/main.js
const abilities = {
seeImage: (user) => {
return typeof user.id === "string";
},
};
Then in your component, use the v-gate:allows=”’seeImage’”
directive
<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
We want pass it to a v-if or a v-show directive like
<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
//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
Top comments (0)