I am writing a simple tooltip component. The contents of the tooltip are defined with a vue slot. I want the contents of the slot to be able to signal a close of the tooltip, the same way the tooltip can close itself from its own template.
Here’s example code:
Vue.component('vtip', { created: function() { this.$on('closeDialog', function(a,b,c) { console.log('[closeDialog in vtip]', a,b,c,this); this.closeDialog(); // idfk }); }, props: ['button_text'], template: '#vtip_template', methods: { closeDialog: function(event) { var $dialog = $(this.$refs.dialog); $dialog.fadeToggle('fast'); }, toggleDialog: function(event) { var $button = $(this.$refs.button); var $dialog = $(this.$refs.dialog); var button_left = $button.position().left; var button_top = $button.position().top; var extras = 2; var arrow_size = 10; var new_pos = {left:button_left+'px',top:button_top+$button.height()+arrow_size+extras+'px'}; $dialog.css(new_pos); $dialog.fadeToggle('fast'); }, } }); var v_app; $(document).ready(function () { v_app = new Vue({ el: '#v_app', }); });
body { background:#eee; padding:1em; font-family:Verdana; font-size:11pt; } .dialog { background:#222; color:#eee; border:2px solid white; display:none; box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.2), 0 6px 20px 0 rgba(0, 0, 0, 0.19); border-radius:4px; position:absolute; } .dialog .item-body .item-body { padding:1em 1em; } .dialog .item-body { min-width:10em; padding:0.5em 1em; }
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.3.3/vue.min.js"></script> <script src="https://code.jquery.com/jquery-3.2.1.min.js"></script> <script id="vtip_template" type="x-template"> <span style="position:relative;"> <button @click="toggleDialog" v-text="button_text" ref="button"></button> <div ref="dialog" class="dialog"> <div class='item-body'> <a @click="closeDialog" style="float:right;color:white;padding:1em;" href="#">x</a> <!-- i hate slots --> <slot>...</slot> </div> </div> </span> </script> <DIV id="v_app"> 1. Click Hey.<br> 2. How do I make the "Yes!" button close the component like the "x" link does?<br> <vtip button_text="hey"> <!-- how do i make it so this slot child can close the parent component --> Are you sure? <button>Yes!</button> <!-- @click="closeDialog" does not work (and should not) --> </vtip> </DIV>
Advertisement
Answer
You could add a ref
attribute to the vtip
component tag, and then reference the component’s closeDialog
method via the $refs
object:
<vtip button_text="hey" ref="tip"> Are you sure? <button @click="$refs.tip.closeDialog()">Yes!</button> </vtip>
Vue.component('vtip', { created: function() { this.$on('closeDialog', function(a,b,c) { console.log('[closeDialog in vtip]', a,b,c,this); this.closeDialog(); // idfk }); }, props: ['button_text'], template: '#vtip_template', methods: { closeDialog: function(event) { var $dialog = $(this.$refs.dialog); $dialog.fadeToggle('fast'); }, toggleDialog: function(event) { var $button = $(this.$refs.button); var $dialog = $(this.$refs.dialog); var button_left = $button.position().left; var button_top = $button.position().top; var extras = 2; var arrow_size = 10; var new_pos = {left:button_left+'px',top:button_top+$button.height()+arrow_size+extras+'px'}; $dialog.css(new_pos); $dialog.fadeToggle('fast'); }, } }); var v_app; $(document).ready(function () { v_app = new Vue({ el: '#v_app', }); });
body { background:#eee; padding:1em; font-family:Verdana; font-size:11pt; } .dialog { background:#222; color:#eee; border:2px solid white; display:none; box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.2), 0 6px 20px 0 rgba(0, 0, 0, 0.19); border-radius:4px; position:absolute; } .dialog .item-body .item-body { padding:1em 1em; } .dialog .item-body { min-width:10em; padding:0.5em 1em; }
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.3.3/vue.min.js"></script> <script src="https://code.jquery.com/jquery-3.2.1.min.js"></script> <script id="vtip_template" type="x-template"> <span style="position:relative;"> <button @click="toggleDialog" v-text="button_text" ref="button"></button> <div ref="dialog" class="dialog"> <div class='item-body'> <a @click="closeDialog" style="float:right;color:white;padding:1em;" href="#">x</a> <!-- i hate slots --> <slot>...</slot> </div> </div> </span> </script> <DIV id="v_app"> 1. Click Hey.<br> 2. How do I make the "Yes!" button close the component like the "x" link does?<br> <vtip button_text="hey" ref="tip"> <!-- how do i make it so this slot child can close the parent component --> Are you sure? <button @click="$refs.tip.closeDialog()">Yes!</button> <!-- @click="closeDialog" does not work (and should not) --> </vtip> </DIV>
Alternatively, you could also use a scoped slot, passing the closeDialog
method as a click handler property in the props
.
In the slot definition, bind a clickHandler
property:
<slot :clickHandler="closeDialog">...</slot>
Then, in your parent component, you can access the clickHandler
via the scoped props
like so:
<vtip button_text="hey"> <template scope="props"> Are you sure? <button @click="props.clickHandler">Yes!</button> </template> </vtip>
Vue.component('vtip', { created: function() { this.$on('closeDialog', function(a,b,c) { console.log('[closeDialog in vtip]', a,b,c,this); this.closeDialog(); // idfk }); }, props: ['button_text'], template: '#vtip_template', methods: { closeDialog: function(event) { var $dialog = $(this.$refs.dialog); $dialog.fadeToggle('fast'); }, toggleDialog: function(event) { var $button = $(this.$refs.button); var $dialog = $(this.$refs.dialog); var button_left = $button.position().left; var button_top = $button.position().top; var extras = 2; var arrow_size = 10; var new_pos = {left:button_left+'px',top:button_top+$button.height()+arrow_size+extras+'px'}; $dialog.css(new_pos); $dialog.fadeToggle('fast'); }, } }); var v_app; $(document).ready(function () { v_app = new Vue({ el: '#v_app', }); });
body { background:#eee; padding:1em; font-family:Verdana; font-size:11pt; } .dialog { background:#222; color:#eee; border:2px solid white; display:none; box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.2), 0 6px 20px 0 rgba(0, 0, 0, 0.19); border-radius:4px; position:absolute; } .dialog .item-body .item-body { padding:1em 1em; } .dialog .item-body { min-width:10em; padding:0.5em 1em; }
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.3.3/vue.min.js"></script> <script src="https://code.jquery.com/jquery-3.2.1.min.js"></script> <script id="vtip_template" type="x-template"> <span style="position:relative;"> <button @click="toggleDialog" v-text="button_text" ref="button"></button> <div ref="dialog" class="dialog"> <div class='item-body'> <a @click="closeDialog" style="float:right;color:white;padding:1em;" href="#">x</a> <!-- i hate slots --> <slot :clickHandler="closeDialog">...</slot> </div> </div> </span> </script> <DIV id="v_app"> 1. Click Hey.<br> 2. How do I make the "Yes!" button close the component like the "x" link does?<br> <vtip button_text="hey"> <template scope="props"> Are you sure? <button @click="props.clickHandler">Yes!</button> </template> </vtip> </DIV>