vue 3 global directive click-outside - javascript

what i want is when i click on textarea i want row value to be 10,
If you click outside the I want line 6
how can i do this i am newbie in vue js 3.
I couldn't figure out how to do this.
I couldn't figure out how to do about directives.
<template>
<div>
<textarea :rows="row"></textarea>
</div>
</template>
<script>
export default {
data() {
return {
row: 6
}
}
}
</script>

Try like following snippet:
const clickOutside = {
beforeMount: (el, binding) => {
el.clickOutsideEvent = event => {
if (!(el == event.target || el.contains(event.target))) {
binding.value();
}
};
document.addEventListener("click", el.clickOutsideEvent);
},
unmounted: el => {
document.removeEventListener("click", el.clickOutsideEvent);
},
};
const app = Vue.createApp({
data() {
return {
row: 6,
};
},
methods: {
setRow() {
this.row = 10
},
removeRow() {
this.row = 6
}
}
})
app.directive("clickOut", clickOutside).mount('#demo')
<script src="https://unpkg.com/vue#3.2.29/dist/vue.global.prod.js"></script>
<div id="demo">
<textarea #click="setRow" :rows="row" v-click-out="removeRow"></textarea>
</div>

Related

vuejs onblur directive not working as expected, while should hide the list

I'm trying to make a simple dropdown search select list.
I'd like to hide the name list by clicking outside of it. And the issue is that when I click outside the list of search items by using #blur directive the appropriate list item doesn't fill the input field. It's assumingly because of the #click="selectCategory(category)" is triggered later than #blur="isVisible = false" in the template.
<template>
<div сlass="search-bar" :style="{'position' : (isVisible) ? 'absolute' : 'fixed'}">
<input
type="text"
v-model="input"
#focus="isVisible = true"
// #blur="isVisible = false" doesn't work as required
/>
<div class="search-bar-options" v-if="isVisible">
<div v-for="category in filteredUser" :key="category.id" #click="selectCategory(category)">
<p>{{ category.name }}</p>
</div>
<div v-if="filteredUser.length === 0">
<p>No results found!</p>
</div>
</div>
</div>
</template>
<script>
export default {
data() {
return {
input: "",
selectedItem: null,
categoriesDynamic: [],
isVisible: false,
};
},
mounted() {
fetch("https://jsonplaceholder.typicode.com/users")
.then((res) => res.json())
.then((json) => {
this.categoriesDynamic = json;
});
},
filteredUser() {
const query = this.input.toLowerCase();
if (this.input === "") {
return this.categoriesDynamic;
} else {
return this.categoriesDynamic.filter(category => {
return category.name.toLowerCase().includes(query);
});
}
},
},
methods: {
selectCategory(category) {
this.input = category.name;
this.isVisible = false;
},
},
};
</script>
<style scoped>
.pointer {
cursor: pointer;
}
.show {
visibility: show;
}
.hide {
visibility: hidden;
}
</style>
One solution could be to detect the clicked element. If the clicked element is not an input or a category element, then the dropdown can be closed.
Here is a working demo in which I gave a class "category" to each list item for element detecting purposes.
Vue.config.productionTip = false;
var app = new Vue({
el: '#app',
data() {
return {
input: "",
selectedItem: null,
categoriesDynamic: [],
isVisible: false,
};
},
mounted() {
fetch("https://jsonplaceholder.typicode.com/users")
.then((res) => res.json())
.then((json) => {
this.categoriesDynamic = json;
});
},
created() {
document.addEventListener('click', (e) => {
let isInput = e.target instanceof HTMLInputElement;
let isCategoryEl = e.target.classList.contains('category');
if (isInput || isCategoryEl) return;
this.isVisible = false;
})
},
computed: {
filteredUser() {
const query = this.input.toLowerCase();
if (this.input === "") {
return this.categoriesDynamic;
} else {
return this.categoriesDynamic.filter(category => {
return category.name.toLowerCase().includes(query);
});
}
},
},
methods: {
selectCategory(category) {
this.input = category.name;
this.isVisible = false;
},
},
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<div сlass="search-bar" :style="{'position' : (isVisible) ? 'absolute' : 'fixed'}">
<input
type="text"
v-model="input"
#focus="isVisible = true"
/>
<div class="search-bar-options" v-if="isVisible">
<div v-for="category in filteredUser" :key="category.id" #click="selectCategory(category)">
<p class="category">{{ category.name }}</p>
</div>
<div v-if="filteredUser.length === 0">
<p>No results found!</p>
</div>
</div>
</div>
</div>

Quill.js Edit Email Template

I want to edit my templates via Quill. I'm using bubble theme. So here is my component;
<template>
<QuillEditor #input="onInputChange" theme="bubble" toolbar="essential" id="quill-Editor" #ready="ready"></QuillEditor>
</template>
<script>
import { QuillEditor } from '#vueup/vue-quill'
import '#vueup/vue-quill/dist/vue-quill.bubble.css';
export default {
props: ['value'],
components: {
QuillEditor
},
created(){
console.log();
setTimeout(() => {
document.querySelector('#quill-Editor .ql-editor').innerHTML = this.value
}, 100);
},
methods: {
onInputChange() {
this.$emit('input', document.querySelector('#quill-Editor .ql-editor').innerHTML);
},
ready(editor){
editor.getModule('toolbar').addHandler('image', () => {
const tooltip = editor.theme.tooltip;
const originalSave = tooltip.save;
const originalHide = tooltip.hide;
tooltip.save = function () {
const range = editor.getSelection(true);
const value = this.textbox.value;
if (value) {
editor.insertEmbed(range.index, 'image', value, 'user');
}
};
tooltip.hide = function () {
tooltip.save = originalSave;
tooltip.hide = originalHide;
tooltip.hide();
};
tooltip.edit('image');
tooltip.textbox.placeholder = 'Embed URL';
});
},
}
}
</script>
Value is this html value. But when I do that quill is deleting all tags giving just few blank <p> tag. What should I do? How can I do that?

How to remove event listener from Vue Component

I have a Vue2 component which contains an added eventListener i created on mounted. I was wondering how do i properly remove this listener when the component is destroyed?
<template>
<div>
...
</div>
</template>
<script>
export default {
mounted() {
window.addEventListener('click', (evt) => {
this.handleClickEvent(evt)
})
},
destroyed() {
// window.removeEventListener('click', ????);
},
methods: {
handleClickEvent(evt) {
// do stuff with (evt)
},
},
}
</script>
You can use this.$el for whole component and destroy event like you created it:
Vue.component('Child', {
template: `
<div class="child">
click for event
</div>
`,
mounted() {
this.$el.addEventListener('click', (evt) => {
this.handleClickEvent(evt)
})
},
beforeDestroy() {
console.log('distroyed')
this.$el.removeEventListener('click', (evt) => {
this.handleClickEvent(evt)
})
},
methods: {
handleClickEvent(evt) {
console.log(evt.currentTarget)
// do stuff with (evt)
},
},
})
new Vue({
el: "#demo",
data() {
return {
show: true
}
},
methods: {
toggleShow() {
this.show = !this.show
}
}
})
.child {
height: 150px;
width: 200px;
background: goldenrod;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="demo">
<div>
<button #click="toggleShow">mount/unmount component</button>
<Child v-if="show" />
</div>
</div>
You have to keep a reference to the registered click handler, in order to be able to remove it later:
mounted() {
this.clickHandler = () => { ... };
window.addEventListener('click', this.clickHandler);
}
beforeDestroy() {
window.removeEventListener('click', this.clickHandler);
}
However, you seem to already have this function defined in the component. It's named handleClickEvent. So there's no reason to create an arrow function wrapper around it. You can use it directly:
mounted() {
window.addEventListener('click', this.handleClickEvent);
}
beforeDestroy() {
window.removeEventListener('click', this.handleClickEvent);
}
Another neat feature available in vue2 (and, sadly, not in vue3) is to dynamically register a hook, which allows adding and removing the handler in mounted(), without the need of keeping a reference to it in the component:
mounted() {
const handler = () => { ... }; // local variable
window.addEventListener('click', handler);
this.$once('hook:beforeDestroy',
() => window.removeEventListener('click', handler)
);
}
https://v2.vuejs.org/v2/guide/components-edge-cases.html#Programmatic-Event-Listeners

How to trigger the method on parent component from child using render function in Vuejs 3

How to trigger the clicked method on app2 from ComponentA in this example.
const app = Vue.createApp({});
app.component('ComponentA', {
template: `<button #click="clicked" class='btn'>Click</button>`
});
const app2 = Vue.createApp({
methods: {
clicked() {
this.clickCount += 1;
}
},
render() {
return Vue.h(app.component('ComponentA'), options);
}
}).mount("#App");
From the button click event handler try to emit an event called clicked, in render function define it by prefixing it by on and upper casing the first letter like onClicked: (e) => {...} inside the body of this function run this.clicked
let options = {}
options.baseUrl = "someurl.com";
const app = Vue.createApp({})
app.component('ComponentA', {
template: `
<button #click="$emit('clicked')" class='btn'>Click</button>
`
});
const app2 = Vue.createApp({
methods: {
clicked() {
console.log("clicked !!!")
this.clickCount += 1;
}
},
render() {
return Vue.h(app.component('ComponentA'), {
onClicked: (e) => {
this.clicked()
}
}, options)
}
}).mount("#app");
<script src="https://unpkg.com/vue#3.0.0-rc.11/dist/vue.global.prod.js"></script>
<div id="app" someVariable='some value'>
</div>

Vue binding parent and child components

How to binding parent's model to child in Vue.js?
These codes below is works fine. if i fill the input manually, then child's model return it's value to the parent's model.
But the issue is, if the data set from AJAX request in a parent, the input doesn't automatically filled.
Can anyone help me on this?
Form.vue
<template>
<form-input v-model="o.name" :fieldModel="o.name" #listenChanges="o.name = $event"/>
<form-input v-model="o.address" :fieldModel="o.address" #listenChanges="o.address = $event"/>
</template>
<script>
import FormInput from '../share/FormInput.vue'
export default {
data () {
return {
o: {
name: '',
address: ''
}
}
},
components: { 'form-input': FormInput },
created: function() {
axios.get('http://api.example.com')
.then(response => {
this.o.name = response.data.name
this.o.address = response.data.address
})
.catch(e => { console.log(e) })
}
}
</script>
FormInput.vue
<template>
<input type="text" v-model='fieldModelValue' #input="forceUpper($event, fieldModel)">
</template>
<script>
export default {
props: ['fieldModel'],
data() {
return {
fieldModelValue: ''
}
},
mounted: function() {
this.fieldModelValue = this.fieldModel;
},
methods: {
forceUpper(e, m) {
const start = e.target.selectionStart;
e.target.value = e.target.value.toUpperCase();
this.fieldModelValue = e.target.value.toUpperCase();
this.$emit('listenChanges', this.fieldModelValue)
}
}
}
</script>
Things are more straightforward if you take advantage of v-model in components.
If you put v-model on a component, the component should take a prop named value, and should emit input events to trigger it to update.
I like to make a computed to hide the event emitting, and allow me to just v-model the computed inside my component.
new Vue({
el: '#app',
data: {
o: {
name: '',
address: ''
}
},
components: {
'form-input': {
template: '#form-input',
props: ['value'],
computed: {
fieldModelValue: {
get() {
return this.value;
},
set(newValue) {
this.$emit('input', newValue.toUpperCase());
}
}
}
}
},
// Simulate axios call
created: function() {
setTimeout(() => {
this.o.name = 'the name';
this.o.address = 'and address';
}, 500);
}
});
<script src="//unpkg.com/vue#latest/dist/vue.js"></script>
<div id="app">
Name ({{o.name}})
<form-input v-model="o.name"></form-input>
Address ({{o.address}})
<form-input v-model="o.address"></form-input>
</div>
<template id="form-input">
<input type="text" v-model='fieldModelValue'>
</template>
The mounted() hook is blocking subsequent updates from the parent.
Remove mounted and change v-model to 'fieldModel'
<template>
<input type="text" :value='fieldModel' #input="forceUpper($event, fieldModel)">
</template>
<script>
export default {
props: ['fieldModel'],
data() {
return {
fieldModelValue: ''
}
},
// mounted: function() {
// this.fieldModelValue = this.fieldModel;
// },
methods: {
forceUpper(e, m) {
const start = e.target.selectionStart;
e.target.value = e.target.value.toUpperCase();
this.fieldModelValue = e.target.value.toUpperCase();
this.$emit('listenChanges', this.fieldModelValue)
}
}
}
</script>
Demo CodeSandbox

Categories

Resources