Vuejs conditional rendering v-if - vue.js

I have this part of code wherein in my view theres a condition if I want to show it or not
<div v-if="toShow" ref="target"></div>
and in my javascript code I trigger toShow to true
this.toShow = true
this.$refs.target // always null
but when I use setTimeout() the value is not null
I need a solution wherein I dont want to use setTimeout() because I'm toggling toShow everytime for my transition so what happens is a have a lot of nested setTimeout() in my code.

You can use $nextTick which waits until the next DOM update cycle. It should be much better than setTimeout because it gets called quickly after the DOM updates rather than a specific time later.
I've created a fiddle below showing it working.
new Vue({
el: "#app",
data: () => {
return {
show: false
};
},
methods: {
toggleShow() {
this.show = !this.show;
console.log(this.$refs.target);
this.$nextTick(() => {
console.log(this.$refs.target);
});
}
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<button #click="toggleShow">Toggle Show</button>
<div v-if="show">
<h5>Showing</h5>
<div ref="target"></div>
</div>
</div>

Related

VueJs: bind `v-on` on a custom component to replace an existing one

In order to ease the styling of my page, I'd like to create a bunch of mini components like, and exploit how attributes are merged in VueJs. So for example, here is a minimal js file also hosted on this JSFiddle:
Vue.component('my-button', {
template: '<button style="font-size:20pt;"><slot></slot></button>'
})
var app = new Vue({
el: "#app",
data: {
message: "world",
},
methods: {
sayHello: function () {
alert("Hello");
}
}
})
and then in my html I just want to use <my-button> instead of button:
<div id="app">
Hello {{message}} <my-button #click="sayHello" style="color:red;">Style works, but not click</my-button> <button v-on:click="sayHello" style="color:red;">Both works</button>
</div>
Unfortunately, it seems that attributes are merged, but not listeners, so it means that I can't do v-on:click on my new button... Any way to make it possible?
Thanks!
-- EDIT --
I saw the proposition of Boussadjra Brahim of using .native, and it works, but then I found this link that explains why it's not a great practice and how to use v-on="$listeners" to map all listeners to a specific sub-button. However, I tried, to just change my template with:
template: `<button style="font-size:20pt;" v-on="$listeners"><slot></slot></button>`,
but I get an error:
Vue warn: Property or method "$listeners" is not defined on the instance but referenced during render. Make sure to declare reactive data properties in the data option."
Here is the JSFiddle.
Your fiddle didn't work because you were using an old version of Vue, $listeners was added in Vue 2.4.0.
Here's a demo:
Vue.component('my-button', {
template: '<button style="color: red" v-on="$listeners"><slot/></button>'
})
new Vue({
el: '#app',
methods: {
sayHello() {
alert('Hello')
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<my-button #click="sayHello">Custom Button</my-button>
<button #click="sayHello">Ordinary Button</button>
</div>

Vue - Change in the state does not re render the template?

I'm trying to understand the basics of Vue and so far what I understand is every time any of the states in the data property changes, the template or the component should re render. Here is the code snippet I'm working with.
index.html
<div id="app">
<h3>Generator</h3>
<div>
Input:
<input #input="onInput"/>
</div>
<div>
Output:
{{test()}}
</div>
</div>
main.js
new Vue({
el:'#app',
data: {
textInput: ''
},
methods: {
onInput(event){
this.textInput = event.target.value
},
test(){
console.log("Test running")
}
}
})
What I expected to happen?
Since I'm updating the textInput data property with every keystroke, I thought that since the template would re render itself, I would see the Test running message in the console every time I hit a key and since the page would re render every time, I would see the input field as blank.
What currently happens
I see the test function run only once when I run the code.
I don't see a blank input field with every key stroke
The DOM does not depend on textInput, so changes to it do not cause a re-render. If the render function uses the variable, you will get a re-render when the variable changes.
new Vue({
el:'#app',
data: {
textInput: ''
},
methods: {
onInput(event){
this.textInput = event.target.value;
},
test(){
console.log(this.textInput);
}
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<h3>Generator</h3>
<div>
Input:
<input #input="onInput"/>
</div>
<div>
Output:
{{textInput.length}}
{{test()}}
</div>
</div>

Extend Vue.js v-on:click directive

I'm new to vuejs. I'm trying to create my first app. I would like to show a confirm message on every click on buttons.
Example:
<button class="btn btn-danger" v-on:click="reject(proposal)">reject</button>
My question is: Can I extend the v-on:click event to show the confirm everywhere? I would make my custom directive called v-confirm-click that first executes a confirm and then, if I click on "ok", executes the click event. Is it possible?
I would recommend a component instead. Directives in Vue are generally used for direct DOM manipulation. In most cases where you think you need a directive, a component is better.
Here is an example of a confirm button component.
Vue.component("confirm-button",{
props:["onConfirm", "confirmText"],
template:`<button #click="onClick"><slot></slot></button>`,
methods:{
onClick(){
if (confirm(this.confirmText))
this.onConfirm()
}
}
})
Which you could use like this:
<confirm-button :on-confirm="confirm" confirm-text="Are you sure?">
Confirm
</confirm-button>
Here is an example.
console.clear()
Vue.component("confirm-button", {
props: ["onConfirm", "confirmText"],
template: `<button #click="onClick"><slot></slot></button>`,
methods: {
onClick() {
if (confirm(this.confirmText))
this.onConfirm()
}
}
})
new Vue({
el: "#app",
methods: {
confirm() {
alert("Confirmed!")
}
}
})
<script src="https://unpkg.com/vue#2.2.6/dist/vue.js"></script>
<div id="app">
<confirm-button :on-confirm="confirm" confirm-text="Are you sure?">
Confirm
</confirm-button>
</div>
I do not know of a way to extend a directive. It is easy enough to include a confirm call in the click handler. It won't convert every click to a confirmed click, but neither would writing a new directive; in either case, you have to update all your code to use the new form.
new Vue({
el: '#app',
methods: {
reject(p) {
alert("Rejected " + p);
}
}
});
<script src="//cdnjs.cloudflare.com/ajax/libs/vue/2.3.4/vue.min.js"></script>
<div id="app">
<button class="btn btn-danger" #click="confirm('some message') && reject('some arg')">reject</button>
</div>

Handle Bootstrap modal hide event in Vue JS

Is there a decent way in Vue (2) to handle a Bootstrap (3) modal hide-event?
I found this as a JQuery way but I can't figure out how to capture this event in Vue:
$('#myModal').on('hidden.bs.modal', function () {
// do something…
})
Adding something like v-on:hide.bs.modal="alert('hide') doesn't seem to work.
Bootstrap uses JQuery to trigger the custom event hidden.bs.modal so it is not easily caught by Vue (which I believe uses native events under the hood).
Since you have to have JQuery on a the page to use Bootstrap's native modal, just use JQuery to catch it. Assuming you add a ref="vuemodal" to your Bootstrap modal you can do something like this.
new Vue({
el:"#app",
data:{
},
methods:{
doSomethingOnHidden(){
//do something
}
},
mounted(){
$(this.$refs.vuemodal).on("hidden.bs.modal", this.doSomethingOnHidden)
}
})
Working example.
Please see https://bootstrap-vue.js.org/docs/components/modal#overview
There you can find event "hide" or "hidden"
So you can bind this event:
<b-modal ref="someModal" #hide="doSometing">
One option is to tie it to a variable:
data: function(){
return {
showModal: false
//starts as false. Set as true when modal opens. Set as false on close, which triggers the watch function.
},
watch: {
showModal: function(){
if(this.showModal == false){
// do something
},
}
HTML
<button id="show-modal" #click="showModal = true">Show Modal</button>
//later if using a component
<modal v-if="showModal" #close="showModal = false">
// or alternatively in the bootstrap structure
<div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal" #click="showModal = false">Close</button>
</div>
This may be late but another way if you are using a custom modal component (Modal.vue) you have created is to
create a method in mounted to catch the event of closure (doesn't have to be the same name as below)
mounted: function(){
this.triggerHidden();
}
create the method
methods: {
triggerHidden: function(){
var self = this;
if( $('#getModal').length ){
$('#getModal').on('hidden.bs.modal', function(){
//catch the native bootstrap close event and trigger yours
self.#emit('modal-close');
});
}
}
}
now call use your custom event with your custom/reusable modal component
<custom-modal #modal-close="doSomething"></custom-modal>
The method doSomething will be called when the modal closes. You can also use the approach to hijack the other jquery event so its a little more manageable.
Maybe creating a Custom Vue Directive can help:
Vue.directive('bsevent', {
bind: function bsEventCreate(el, binding, vnode) {
let method = binding.value || (() => { });
$(el).on(binding.arg.replaceAll(/_/g, "."), (event) => { method(event); });
},
unbind(el, binding) {
$(el).off(binding.arg.replace(/_/, "."));
},
});
And then just use it on the element you wish (this example is on a bootstrap collapsible, but you could use it to any other bootstrap event):
<div id="myCollapsible" class="collapse" v-bsevent:hidden_bs_collapse="methodToCall">
...
</div>
The only thing to remember is to register the event with underscores instead of dots (show.bs.modal => show_bs_modal).
If working with bootstrap-vue then below code snippet will be helpful:
export default {
mounted() {
this.$root.$on('bv::modal::hide', (bvEvent, modalId) => {
console.log('Modal is about to be shown', bvEvent, modalId)
})
}
}
for other events please refer to the official docs.
Just use native addEventListener (Vue 3, Composition API)
template:
<div ref="modalElement" class="modal">
...
</div>
script:
import { Modal } from "bootstrap"
import { onMounted, ref } from "vue";
const modalElement = ref(null)
let modal = null;
onMounted(() => {
modal = new Modal(modalElement.value)
modalElement.value.addEventListener("hidden.bs.modal", onHidden)
})
function onHidden() {
// do something…
}
We can also use this simple approach like this example
<template>
<div>
<button #click="openModal = true">Open Modal</button>
<div v-if="openModal">
<div class="modal-background"></div>
<div class="modal-content">
<button #click="openModal = false">Close Modal</button>
</div>
</div>
</div>
</template>
<script>
export default {
data() {
return {
openModal: false
}
}
}
</script>

Vuejs: Callback after render

I have a Bootstrap popover that I want to attach to an element that has a conditional render; therefore, I must trigger $().popover() after the element has been attached to the DOM.
Is there a way to trigger a callback after a v-if statement inserts the elements into the DOM?
Use this in vuejs 2:
updated: function() {
$('[data-toggle="tooltip"]').tooltip();
},
take a look here
The right way to do this, is making it a directive, so you can hook into the life cycle of a DOM element.
Using nextTick is not the right way to do it for a few reasons, it can break if the DOM reacts and re render a part of your view. You are not destroying tooltips after initialisation. This can break because nextTick is async, and something in between render and nextTick can change your DOM state.
https://v2.vuejs.org/v2/guide/custom-directive.html
/* Enable Bootstrap popover using Vue directive */
Vue.directive('popover', {
bind: bsPopover,
update: bsPopover,
unbind (el, binding) {
$(el).popover('destroy');
}
});
function bsPopover(el, binding) {
let trigger;
if (binding.modifiers.focus || binding.modifiers.hover || binding.modifiers.click) {
const t = [];
if (binding.modifiers.focus) t.push('focus');
if (binding.modifiers.hover) t.push('hover');
if (binding.modifiers.click) t.push('click');
trigger = t.join(' ');
}
$(el).popover('destroy'); //update
$(el).popover({
title: typeof binding.value==='object'? binding.value.title : undefined,
content: typeof binding.value==='object'? binding.value.content : binding.value,
placement: binding.arg,
trigger: trigger,
html: binding.modifiers.html
});
}
//DEMO
new Vue({
el: '#app',
data: {
foo: "Hover me",
bar: "There",
baz: {content: "<b>Hi</b><br><i>There</i>", title: "Test"},
}
});
<link href="https://unpkg.com/bootstrap#3.3.7/dist/css/bootstrap.css" rel="stylesheet"/>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://unpkg.com/bootstrap#3.3.7/dist/js/bootstrap.js"></script>
<script src="https://unpkg.com/vue#2.5.16/dist/vue.js"></script>
<div id="app">
<h4>Bootstrap popover with Vue.js Directive</h4>
<br>
<input v-model="foo" v-popover.hover="foo"/>
<button v-popover.click="bar">Click me</button>
<button v-popover.html="baz">Html</button>
<br>
<button v-popover:top="foo">Top</button>
<button v-popover:left="foo">Left</button>
<button v-popover:right="foo">Right</button>
<button v-popover:bottom="foo">Bottom</button>
<button v-popover:auto="foo">Auto</button>
</div>
Vue.nextTick() defers the execution of the callback to be executed after the next update of the DOM, see: VueJS API reference