How to make custom directive in Vue.js 3? - javascript

I am following the guide of Custom Directive to create a custom directive in Vue.js 3.
I made a directive to change background of an element.
Home.vue includes usage of the custom directive and main.js includes the custom direcive definition.
"Home.vue"
<template>
<p v-highlight="yellow">Home</p>
</template>
"main.js"
import { createApp } from 'vue'
import App from './App.vue'
const app = createApp(App)
app.mount('#app');
app.directive("highlight",{
beforeMount(el, binding){
el.style.background = binding.value
}
});
But I get the following error in console:
"Cannot read property 'created' of undefined"
Does anyone help me?

simply change the order of mount/directive
import { createApp } from 'vue'
import App from './App.vue'
const app = createApp(App)
app.directive("highlight",{
beforeMount(el, binding){
el.style.background = binding.value
}
});
app.mount('#app');
or you can do
app.directive("highlight",{
beforeMount(el, binding){
el.style.background = binding.value
}
}).mount('#app');

Related

Create Vanilla JS-compatible library with a Vue UI

I've got a Vue 3 project using Vite. When run in development mode, it opens a modal which makes some API calls.
main.js
import { createApp } from 'vue'
import { VTooltip } from 'floating-vue'
import store from '#/store'
import App from './App.vue'
import 'floating-vue/dist/style.css'
import './tailwind.css'
const app = createApp(App)
app.use(store)
app.directive('tooltip', VTooltip)
app.mount('#app')
App.vue
<template>
<Modal />
</template>
<script setup>
import Modal from './components/Modal.vue'
</script>
I want to make this project available as a library which can be installed with a <script> tag, or via NPM install, exposing a function which can be called to mount the modal, for example:
import myApi from '#me/myapi'
import '#me/myapi/dist/style.css'
myApi.show({ value: 'something' })
or
<link rel="stylesheet" href="https://unpkg.com/#me/myapi/dist/main.css">
<script src="https://unpkg.com/#me/myapi/dist/main.umd.js" />
myApi.show({ value: 'something' })
This call would initialise the library, mount it to the DOM and show the modal. I've found Vite has a library mode, which should support what I want.
My current thinking is to refactor main.js to export an initialiser function, and change the bundle entry to a class which calls the initializer:
main.js
// ...
export const initialize = (el, args) => {
const app = createApp(App)
app.use(store)
app.directive('tooltip', VTooltip)
app.mount(el)
return app
}
entrypoint.js
import { initialize } from './main'
class MyApi {
show(args) {
const element = document.createElement('div')
this.app = initialize(element, args)
}
}
export default new MyApi()
But I'm stuck at how you'd set the global variable for <script> integration (other than window.myApi = new MyApi()).
Is this the right way to go about this?

How to register a global directive in Vue for entire app

How can i register a Vue directive so it's available in all of my components of my Vue application? I'm using Vue 2
The docs say you can register global but where in my project do I add this Vue.directive(....)
src/directives/TestMe.js
import Vue from 'vue'
export default Vue.directive('test-me', {
inserted(el) {
el.style.backgroundColor = 'red'
},
})
src/components/SignInForm.vue
<template>
<div>
<code v-test-me>
Hello World
</code>
</div>
</template>
</script>
I know the current way of doing this by doing the following. However it's not practical if I plan on using my directive in multiple components and pages of my application.
<template>
<div>
<code v-test-me>
Hello World
</code>
</div>
</template>
<script>
import TestMe from '../../directives/TestMe'
export default {
directives: {
TestMe
}
}
</script>
Updated
Updated post based on answers below.
I've created a different files for each directive and then have an index.js file where i import all my directives. How can i initiate that index.js file in the directives folder at the start of my application?
// src/directives/TestMe.js
export default testMe = {
inserted(el) {
el.style.backgroundColor = 'red'
},
}
// src/directives/index.js
import testMe from './TestMe'
export default {
install(Vue) {
Vue.directive('test-me', testMe)
// Vue.directive('other-directive', myOtherDirective)
},
}
// src/main.js
import Vue from 'vue'
import App from './App.vue'
import ???? from './directives'
const app = createApp(App)
app.use(????)
You have globalDirectives...where does that name come from?
EDIT: I didn't read that OP is using Vue 2.
To call .use(), you need to export object with function
install(Vue)
If you want the directive as a separate file.
// src/directives/TestMe.js
const testMe = {
inserted(el) {
el.style.backgroundColor = 'red'
},
}
export default testMe
Here is where you register all the directives. You can import files or just define your directives here.
// src/directives/index.js
import testMe from './TestMe'
// const myOtherDirective = {
// ...
// }
export default {
install (Vue) {
Vue.directive('test-me', testMe)
// Vue.directive('other-directive', myOtherDirective)
}
}
Here is how to register directives in Vue 2
You can register the global directives in main.js with Vue.use
// src/main.js
import Vue from 'vue'
import App from './App.vue'
import globalDirectives from './directives'
Vue.use(globalDirectives)
const app = new Vue({
router,
store,
render: h => h(App),
})
app.$mount('#app')
Here is how to register directives in Vue 3
You can register the global directives in main.js after createApp
// src/main.js
import Vue from 'vue'
import App from './App.vue'
import globalDirectives from './directives'
const app = createApp(App)
app.use(globalDirectives)

How to access "this" keyword in <script setup> vue SFC

I am working on a simple scaffold for vite, vue and vuetify with typescript and I wanted to use the script setup version of SFC vue
<script setup lang="ts">
One thing that I can't figure out is how to access "this" keyword properties?
for example in my old vue projects i could write this
this.$vuetify.themes.light.colors.primary
so i had the ability to access $vuetify anywhere in the component, but now in script setup mode "this" keyword is undefined;
How to access "this" properties?
The setup keyword in the script tag is a syntax sugar for:
const myComponent = createComponent({
setup() {
const foo = "may-the-force";
let bar = "be-with-you";
return {
foo,
bar
}
}
})
So naturally, in a setup function, you won't need this keyword because now you can just do:
bar = "be-not-with-you";
return {
foo,
bar
}
Now, when you initiated your Vuetify framework an instance is going to be kept somewhere. Something like this:
import Vue from "vue";
import { createVuetify } from 'vuetify'
Vue.use(Vuetify);
export const vuetify = createVuetify({ theme: {} });
Now that you have stored your vuetify instance somewhere you can import it just like you would do any other javascript/typescript file:
<script setup lang="ts">
import { vuetify } from "path/to/vuetify/instance";
console.log(vuetify.themes.light.colors.primary);
// You can even set dark mode if you prefer to
vuetify.framework.theme.dark = true;
</script>
Edit
I'm guessing that things are a little bit different in Vue 3. After researching a little bit you can get the current Vue instance by using getCurrentInstance
<script setup>
import { getCurrentInstance } from 'vue'
const app = getCurrentInstance()
// it should be here in this instance the vuetify object with its properties
console.log(app);
</script>
Using provide and inject
For e.g. I am using marcoschulte/vue3-progress package to show a loading bar at the top whenever routing happens.
According to vue3-progress docs, I can use this.$progress inside the script tag, but the this keyword is unavailable inside .
So in this scenario, I had to use provide and inject for props drilling.
In main.js or app.js (in laravel)
require('./bootstrap');
import { createApp } from 'vue'
import App from './views/App.vue'
import store from './store/index'
import router from './router'
import Vue3Progress from 'vue3-progress'
const options = {
position: "fixed",
height: "3px",
color: "#9d5e32",
}
let app = createApp(App)
app.use(store)
.use(router)
.use(Vue3Progress, options)
// $progress is set automatically when I import vue3-progress at top
.provide('progressBar', app.config.globalProperties.$progress)
.mount('#app')
In any SFC
<template>
<vue3-progress />
<TopNav />
<router-view></router-view>
<Footer />
</template>
<script setup>
import Header from '../components/admin/Fixed/Header.vue'
import Footer from '../components/admin/Fixed/Footer.vue'
import { inject } from 'vue-demi'
import { useRouter } from 'vue-router'
let router = useRouter()
let progressBar = inject('progressBar')
router.beforeEach((to, from, next) => {
progressBar.start()
next()
})
router.afterEach((to, from) => {
progressBar.finish()
})
</script>

[Vue warn]: Failed to resolve component when tried to create global component

I new on Vue Typescript. I've tried to create global components but I got a warning and component not loaded on the template. This how I tried to create global components
App.vue
import { createApp } from "vue"
import App from "./App.vue"
import "./registerServiceWorker"
import "./globalComponents"
import router from "./router"
import store from "./store"
createApp(App)
.use(store)
.use(router)
.mount("#app")
globalComponents.ts
import { createApp } from "vue"
const app = createApp({})
// Forms
app.component("ui-input", () => import("#/components/core/ui/Input.vue"))
Input.vue
<template lang="pug">
.ui-input
input(v-model="$attrs" v-on="$listeners")
</template>
<script lang="ts">
import { defineComponent } from "vue"
export default defineComponent({
inheritAttrs: false
})
</script>
Hope you all can help me, Thanks in advance
As of Vue 3, if you create an app using createApp, it will be a standalone Vue App instance. So if you need to add a global component then you will need to add it on the app object created from createApp, here's the code for that:
const app = createApp({});
app.component('my-component-name', MyComponent) // <-- here you can register.
app.mount("#app");
But if there are a lot of components then adding them in the main.ts file will be a mess, so we can create another file, like you did, so:
Your current globalComponents.ts
import { createApp } from "vue"
const app = createApp({})
// Forms
app.component("ui-input", () => import("#/components/core/ui/Input.vue"))
The Problem
But notice here's a mistake. What is it? You created another app using createApp. As I referred earlier that if you need to create a global component, you can only create on the same instance.
Fix
As we know the problem here is that we are creating another instance, which is again a new and standalone instance, so we need to figure out the way we can have the same app instance in globalComponents.ts as well, so we will pass the app from main-ts to globalComponents.ts, like:
globalComponents.ts
import { App } from "vue";
// register components
export const registerComponents = (app: App): void => {
app.component("ui-input", () => import("#/components/core/ui/Input.vue"));
}
And now you can call registerComponents in main.ts as:
main.ts
const app = createApp(App)
.use(store)
.use(router);
registerComponents(app); // <-- here you go
app.mount("#app");
You will still get something like:
[Vue warn]: Invalid VNode type: undefined (undefined).
You can read more here about how to define an async component in vue 3. To fix that error you will need to wrap your import in defineAsyncComponent as:
globalComponents.ts
import { defineAsyncComponent } from "vue";
// register components
export const registerComponents = (app) => {
app.component(
"ui-input",
defineAsyncComponent(() => import("#/components/Input.vue"))
);
};

How to load quasar framework Globally

I am new to Quasar framework. Could someone explains how load quasar-components in Globally use. (every where in my application)
My main.js is like:
import Vue from 'vue'
import Quasar from 'quasar'
import router from './router'
require(`quasar/dist/quasar.${__THEME}.css`)
Vue.config.productionTip = false
Vue.use(Quasar) // Install Quasar Framework
if (__THEME === 'mat') {
require('quasar-extras/roboto-font')
}
import 'quasar-extras/material-icons'
// import 'quasar-extras/ionicons'
// import 'quasar-extras/fontawesome'
// import 'quasar-extras/animate'
Quasar.start(() => {
/* eslint-disable no-new */
new Vue({
el: '#q-app',
router,
render: h => h(require('./App').default)
})
})
Unknown custom element: <q-btn> - did you register the component correctly?
For recursive components, make sure to provide the "name" option.
found in
---> <App> at src\App.vue
<Root>
Whenever you're using any Quasar elements (eg. q-btn, q-select), you need to import and export it in your .vue file.
Example, for a <q-btn> to display, you might use
<q-btn > Confirm </q-btn>
But to display that, you need to include following into your .vue file. Like:
import {
QSelect,
QBtn
} from 'quasar'
export {
QSelect,
QBtn
} from 'quasar'
Like this, you will be registering all your components.
In my projects I import and use with components like this
import Quasar, { QBtn, QSelect } from 'quasar-framewok';
Vue.use(Quasar, {
components: { QBtn, QSelect }
});
Only for test case import all
import Quasar, * as All from 'quasar';
Vue.use(Quasar, {
components: All,
directives: All
});
See Quasar docs

Categories