Writing a custom directive on Vuejs 3, composition API in order to detect outside click - vuejs3

my first time trying directive with vue js3.
My goal: To detect a click outside the component with the directive and Vuejs 3, composition API.
My expected result: To change a Boolean value in each click outside the component 'CustomeMultiSelect.vue'.
My actual result: In any click, the Boolean value is changed.
Here is my 'App.vue'
<script lang="ts">
import CustomeMultiSelect from "./components/CustomeMultiSelect.vue";
import { ref } from "vue";
export default {
components: {
CustomeMultiSelect,
},
directives: {
"click-outside": {
mounted: function (el, binding) {
console.log("in directive");
const ourClickEventHandler = (event) => {
if (!el.contains(event.target) && el !== event.target) {
binding.value(event);
}
};
el.__vueClickEventHandler__ = ourClickEventHandler;
document.addEventListener("click", ourClickEventHandler);
},
unmounted: function (el) {
document.removeEventListener("click", el.__vueClickEventHandler__);
},
},
},
setup() {
let bool = ref(true);
function test() {
bool.value = !bool.value;
console.log(bool.value);
}
return {
test,
};
},
};
</script>
<template>
<div v-click-outside="test">
<CustomeMultiSelect/>
</div>
</template>
I defined directive that on 'mounted' Hook will attached event 'click' to each element in the screen -> 'v-click-outside' on <'CustomeMultiSelect.vue'/>
Component 'CustomeMultiSelect.vue' is a child component of 'App.vue'.
('CustomeMultiSelect.vue' has 3 childs).

Related

Storybook Vue3 - Work with v-model in stories

I have a question regarding Storybook and Vue components with v-models. When writing a story for let's say an input component with a v-model i want a control reflecting the value of this v-model. Setting the modelValue from the control is no problem, but when using the component itself the control value stays the same. I am searching the web for a while now but i can't seem to find a solution for this.
A small example:
// InputComponent.vue
<template>
<input
type="text"
:value="modelValue"
#input="updateValue"
:class="`form-control${readonly ? '-plaintext' : ''}`"
:readonly="readonly"
/>
</template>
<script lang="ts">
export default {
name: "GcInputText"
}
</script>
<script lang="ts" setup>
defineProps({
modelValue: {
type: String,
default: null
},
readonly: {
type: Boolean,
default: false
}
});
const emit = defineEmits(['update:modelValue']);
const updateValue = (event: Event) => {
const target = event.target as HTMLInputElement;
emit('update:modelValue', target.value);
}
</script>
In Storybook:
Does anyone have a solution to make this working?
Thanks in advance!
In my case, I have a custom select input that uses a modelValue prop.
I tried this and worked for me:
at my-component.stories.js:
import { ref } from 'vue'
import MyComponent from './MyComponent.vue'
export default {
title: 'Core/MyComponent',
component: MyComponent,
argTypes: { }
}
const Template = (args) => ({
components: { MyComponent },
setup() {
let model = ref('Javascript')
const updateModel = (event) => model.value = event
return { args, model, updateModel }
},
template: '<my-component v-bind="args" :modelValue="model" #update:modelValue="updateModel" />'
})
export const Default = Template.bind({})
Default.args = {
options: [
'Javascript',
'PHP',
'Java'
]
}

How to render an html string with vuejs inside in Nuxt3 / Vue3

I'm trying to write a component in Nuxt3 which will allow me to output a string of html (that contains vue elements).
Here is what I have so far for the component / plugin
plugins/RenderVueString.js
export default defineNuxtPlugin(nuxtApp => {
nuxtApp.vueApp.component('RenderVueString', {
props: ['html'],
render(h) {
return h({
template: `<div class="RenderVueString">${this.html}</div>`,
})
}
})
})
And then in pages/index.vue
<template>
<RenderVueString :html="vueHTML" />
</template>
<script>
export default {
data() {
return: {
vueHTML: `<div>This is some vue HTML {{testVar}} <a #click="testFunction()">Run Function</a></div>`,
testVar: 'Var Value Here'
}
},
methods: {
testFunction() {
console.log('test function ran');
}
}
}
</script>
I get this error: TypeError: h is not a function
So I tried adding this to the top of the plugins/RenderVueString:
import {h} from 'vue';
After that there is no console errors, but nothing renders.
I did try rendering something simple with h like this: h('div', 'Hello') and it did output that, but I can't figure out how to output complex html with embedded Vue.
Was able to figure this out by adding the following to nuxt.config.ts
hooks: {
'vite:extendConfig': (config, {isClient, isServer}) => {
if(isClient) {
config.resolve.alias.vue = 'vue/dist/vue.esm-bundler'
}
}
},
nitro: {
commonJS: {
dynamicRequireTargets: [
'./node_modules/#vue/compiler-core',
'./node_modules/#vue/compiler-dom',
'./node_modules/#vue/compiler-ssr',
'./node_modules/vue/server-renderer',
'./node_modules/vue'
]
},
},
alias: {
'#vue/compiler-core': '#vue/compiler-core',
'#vue/compiler-dom': '#vue/compiler-dom',
'#vue/compiler-ssr': '#vue/compiler-ssr',
'vue/server-renderer': 'vue/server-renderer',
'estree-walker': 'estree-walker',
'#babel/parser': '#babel/parser'
},
And then in the plugins/RenderVueString.js
import { h, compile } from 'vue';
export default defineNuxtPlugin(nuxtApp => {
nuxtApp.vueApp.component('RenderVueString', {
props: ['html'],
render() {
return h(compile(this.html), {$emit: this.$emit});
}
})
})
Allows this on the a template:
<template>
<RenderVueString :html="vueHTML" #runFunction="testFunction()" />
</template>
<script>
export default {
data() {
return: {
vueHTML: `<div>This is some vue HTML <a #click="$emit('runFunction')">Run Function</a></div>`,
}
},
methods: {
testFunction() {
console.log('test function ran');
}
}
}
</script>
I could also pass in variables as props into the RenderVueString component if needed.
This type of functionality is very useful if you're trying to allow some advanced coding from items being pulled from a database / CMS.

Event #change is fire but cant get it in Vue Component Verte

can't catch the changed event
my template code:
<template>
<verte #change="changeColor(1)"></verte>
</template>
<script>
import Verte from 'verte';
export default {
components: { verte }
methods: {
changeColor(id) {
console.log(id)
},
}
</script>
in vue inspector event #change starts..
tried changing this line
from import Verte from 'verte';
to import VertePicker from 'verte';
...
but as a result, only the input event fires, but I just can’t catch the event on changed
maybe something else is needed to correctly trigger the event? and could this be a component bug?
Verte is not supporting change event.
You can call changeColor as below.
<template>
<verte v-model="colorVal"></verte>
</template>
<script>
import Verte from 'verte';
export default {
components: { Verte },
data: () => ({
colorVal: ''
}),
watch: {
// whenever colorVal changes, this function will run
colorVal(currentColor, previousColor) {
// you can call changeColor function here.
changeColor(currentColor);
}
},
methods: {
changeColor(id) {
console.log(id)
},
}
</script>

vue3 setup emit with function click

i want to run a method when emitting how can i get it?
When handleShow(depth is clicked), I want to run collapsed in the medhod in the setup.
or
I want to trigger the function I will write in setup
<MenuLink
:link="items"
:key="items.title"
#click.stop="handleShow(depth)"
/>
<script>
import {ref} from "vue"
import MenuLink from "./MenuLink";
export default {
name: 'MenuItems',
components: {MenuLink},
props: {
items: {type: Object, required: true},
depth: {Number},
selected: {Number},
},
data() {
return {
opensCollapsed: false
};
},
methods: {
collapsed(dep) {
console.log(dep)
}
},
setup(props, {emit}) {
const showDropdown = ref(false);
const handleShow = (depth) => {
emit('clicked', depth)
}
return {
showDropdown,
handleShow,
}
},
};
</script>
emit should only be used if you want to get an event out of your component to its parent (for example, if your component is a custom button and you want its parent to specify what would happen when clicking on it). Otherwise, you can write the code you want inside of handleShow instead of calling emit. You can also change the function name to whatever you want, just make sure it's the same inside the setup method and in the #click.stop property.
In your case (since you just console.log the result):
<MenuLink
:link="items"
:key="items.title"
#click.stop="handleShow(depth)"
/>
<script>
import {ref} from "vue"
import MenuLink from "./MenuLink";
export default {
name: 'MenuItems',
components: {MenuLink},
props: {
items: {type: Object, required: true},
depth: {Number},
selected: {Number},
},
data() {
return {
opensCollapsed: false
};
},
setup(props, {emit}) {
const showDropdown = ref(false);
const handleShow = (depth) => {
console.log(depth)
// do whatever you want here
}
return {
showDropdown,
handleShow,
}
},
};
</script>

Vue 3 compoition API computed function

Trying to switch my code to the new composition API that comes with Vue 3 but I cant get it to work.
export default {
props: {
classProp: {type: String},
error: {type: String},
},
setup(){
// move to here (this is not working)
computed(() => {
const classObject = () => {
return ['form__control', this.classProp,
{
'form__invalid': this.error
}
]
}
})
},
computed: {
classObject: function () {
return ['form__control', this.classProp,
{
'form__invalid': this.error
}
]
}
},
}
skip "computed" all together
you need to use "ref" or "reactive". these are modules:
<script>
import { ref } from 'vue'
setup(){
const whateverObject = ref({ prop: "whatever initial value" });
whateverObject.value.prop= "if you change something within setup you need to access it trough .value";
return { whateverObject } // expose it to the template by returning it
}
</script>
if you want to use classes you import them like in this example of my own:
import { APIBroker } from '~/helpers/APIbroker'
const api = new APIBroker({})
Now "api" can be used inside setup() or wherever

Resources