VueJS <b-modal> inside component won't open on button click - javascript

I use vue-tables-2 and bootstrap-vue. I've created a component which is a column in vue-tables-2 and it consists of button and modal code.
The problem is that this way, the modal is not opening when you click on the button and I don't know why.
EDIT
I found out that when I hardcode buttons attribute,it works. It just doesn't work when v-b-modal.modal-something is generated by vue.
This is a component:
Vue.component('vue-tables-2-product', {
delimiters: ['[[', ']]'],
props: ['data', 'index', 'column'],
template: `<div>
<b-modal v-bind="modal_attrs" title="BootstrapVue">
<p class="my-4">[[ this.data.name ]]</p>
</b-modal>
<b-button #click="log" v-bind="button_attrs">Detail</b-button></div>`,
methods: {
log: function () {
console.log(this.data)
}
},
computed: {
button_attrs() {
return {
[`v-b-modal.modal-${this.data.id}`]: "",
}
},
modal_attrs() {
return {
[`id`]: "modal-"+this.data.id,
}
},
}
})
And this is templates from the vue app.
templates: {
on_stock: 'boolean',
is_active: 'boolean',
name: 'vue-tables-2-product',
import_export_price_diff: 'vue-tables-2-difference'
}
Do you know where is the problem?
EDIT:
I also tried to add this.$bvModal.show(this.data.id) into the log function and nothing happens.
I noticed that

Aren't you passing empty string as your ID?
What's the reason of doing this?
button_attrs() {
return {
[`v-b-modal.modal-${this.data.id}`]: "",
}
},
It's literally :v-b-modal.modal-some-id=""
This dot might be problematic tho.

Vue accepts variables as modifiers on directives (by placing them in square brackets):
<div>
<b-modal v-bind="modal_attrs" title="BootstrapVue">
<p class="my-4">[[ this.data.name ]]</p>
</b-modal>
<b-button #click="log" v-b-modal.[modal_attrs.id]>Detail</b-button>
</div>

Related

Bootstrap Vue Modal Component

I'm looking to make a global Bootstrap Vue Modal component that I can use for all of my modal needs.
The issue I am facing is that it keeps giving me an error regarding prop mutation. You cannot update them directly in the child component. Which makes sense.
This is what I have so far:
<template>
<b-modal
v-model="show"
size="lg"
scrollable
centered
header-class="event-close-modal-header"
#hide="$emit('close')"
#cancel="$emit('close')"
#ok="$emit('close')"
#show="handleShow"
>
<template #modal-title="{}">
<span class="event-close-modal-header-mc2">{{ title }}</span>
</template>
<b-container fluid class="pb-4">
<h1>Content goes here</h1>
<slot />
</b-container>
</b-modal>
</template>
<script>
export default {
props: {
show: {
type: Boolean,
default: false,
required: true,
},
title: {
type: String,
required: true,
},
},
data() {
return {};
},
};
</script>
And in my parent I have something like this:
<ModalHelper
:show.sync="modalOne" // modalOne is a data property
title="One"
#close="modalOne = false"
/>
<button #click="modalOne = true">Open Modal One</button>
Error:
You can't use props in v-model. Try watch it, and copy new value into new variable in data:
data() {
return {
showModal: false
}
},
watch: {
show(newValue) {
this.showModal = newValue;
}
}
now use showModal in your code instead of "show".
However, this is no good approach.
You should open-close bootstrap vue modals using:
$bvModal.show()
$bvModal.hide()
Vue Bootstrap Modal

How can i access the errors computed property on custom input component validated using vee-validate?

I am trying to use VeeValidate in a custom input component.
I tried using $emit on #input and #blur but the validation occurs on next tick and i end up failing to catch the validation on event.
onEvent (event) {
console.log('error length', this.errors.items.length)
if (this.errors.items.length) {
this.hasError = true
this.$emit('has-error',this.errors.items)
} else {
this.hasError = false
this.$emit('on-input', event)
}
}
I also tried injecting the validator from the parent so as to be able to access the errors computed property directly but there might be 1-2-3 levels of nesting between the parent page and the custom input component itself. I would have to inject the validator through all of them and these component are meant to be reusable.
export default {
//in custom input component
inject: ['$validator'],
}
The best idea i got is to watch the errors computed property and emit an event when a change occurs whit the errors in that instance of the component.
watch: {
errors: function (errorsNew) {
console.log('errors watch',errorsNew)
}
},
The problem is that i can't seem to watch the errors computed property introduced by vee-validate.
Some simplified version of code:
parent
<template>
<div id="app">
<CustomInput
:label="'Lable1'"
:value="'value from store'"
:validations="'required|max:10|min:5'"
:name="'lable1'"
/>
<button>Save</button>
</div>
</template>
<script>
import CustomInput from "./components/CustomInput";
export default {
name: "App",
components: {
CustomInput
}
};
</script>
CustomInput
<template>
<div>
<label >{{ label }}</label>
<input :value="value" :name="name" v-validate="validations">
<span v-if="this.errors.items.length">{{this.errors.items[0].msg}}</span>
</div>
</template>
<script>
export default {
name: "HelloWorld",
props: {
label: {
type: String,
required: true
},
value: {
type: String,
default: ""
},
validations: {
type: String,
default: ""
},
name: {
type: String,
required: true
}
},
watch: {
errors: function(errorsNew) {
console.log("errors watch", errorsNew);
this.$emit('has-error')
}
}
};
</script>
Can someone please help me access the validation errors from the custom input?
Update
I created a simple fiddle if anyone finds it easier to test it https://codesandbox.io/s/mqj9y72xx
I think the best way to handle this is to inject $validator into the child component. That seems to be how the plugin recommends it be done: https://baianat.github.io/vee-validate/advanced/#injecting-parent-validator.
So, in your CustomInput, you'd add inject: ['$validator'],.
Then, in the App.vue, you can have this in the template:
<div>
These are the errors for "lable1" in App.vue:
<span v-if="errors.has('lable1')">{{errors.first('lable1')}}</span>
</div>
I think that's really it.
Working example based off your example: https://codesandbox.io/s/pw2334xl17
I realize that you've already considered this, but the inject method searches up the component tree until it finds a $validator instance, so perhaps you should disable automatic injection in your app at the root level, thus every component searching for a validator to inject will all get that one. You could do that using:
Vue.use(VeeValidate, { inject: false });

VueJS 2 show some HTML if a Function prop was passed

Question
In VueJS 2 how do you show some HTML if a Function prop was passed to the component.
Example
<template>
<div v-if="backBtn" #click="$emit('backBtn')">Back Button</div>
</template>
<script>
export default {
props: {
backBtn: Function
}
}
</script>
I can do this by passing a separate prop to key the v-if off of but I'm trying to do this will the one prop.
I created a Fiddle for this issue here
that should work,
you can add more definition with !== undefined
<template>
<div v-if="backBtn !== undefined" #click="$emit('backBtn')">Back Button</div>
</template>
<script>
export default {
props: {
backBtn: {
type: Function,
},
}
}
</script>
but as mentioned, that should work already, so you error may be somewhere else.
after seeing your code, I see what the issue is. it's a case issue
use :back-btn instead of :backBtn
this happens only if you're using vue runtime only (without the compilation)
read more here:
https://v2.vuejs.org/v2/guide/installation.html#Runtime-Compiler-vs-Runtime-only
you can solve it also by passing the function only
https://jsfiddle.net/rz6hyd7b/7/
Vue.component('my-btn', {
props: {
backbtn: {
type: Function
}
},
template: `
<div>
<div v-if="backbtn" #click="backbtn">Back Button</div>
</div>
`
})
var vm = new Vue({
el: '#app',
components: 'my-btn',
methods: {
btnClicked: function(){
console.log('adsf')
}
},
template: `
<div>
Show Btn => <my-btn :backbtn="btnClicked"></my-btn>
</br>
Hidden Btn => <my-btn></my-btn>
</div>
`
});

Vue v-model changes parent data but doesn't change value prop in custom component

someone to help me with this strange issue?
I have the following vue component:
<template lang="pug">
div
p.alert.alert-info {{value}}
button(#click="onChange") Change
</template>
<script>
import Vue from 'vue';
export default {
name: 'z-temp',
props: {
value: {
required: true
}
},
watch: {
value(val) {
console.log(val);
}
},
methods: {
onChange() {
this.$emit('input', Random.id());
}
}
};
</script>
I want to use v-model, but when I use <z-temp v-model="myDataField">, the myDataField changes with success when I click in the Change button, but when I make the inverse and put some value in myDataField, like myDataField: "foo" the custom component gets this.value as undefined instead as foo.
Can anyone help me please?
I am guessing that myDataField is not reactive so the problem is in the parent.
Can you clarify the problem, seems like it is working as it should, ie. the component does not seem to be the problem.
zTemp = Vue.component('ztemp', {
template: '#ztemp',
props: {
value: {
required: true
}
},
watch: {
value(val) {
console.log(val);
}
},
methods: {
onChange() {
this.$emit('input', Math.random());
}
}
});
new Vue({
el: '#app',
components: {
zTemp
},
data: {
myinput: '0.2'
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.4.4/vue.min.js"></script>
<div id="app">
{{myinput}}<br/>
<input v-model="myinput"/><br/>
<ztemp v-model="myinput"/><br/>
</div>
<script type="text/x-template" id="ztemp">
<div>
<p>{{value}}</p>
<button #click="onChange">Change</button>
</div>
</script>
thanks for helping, but I'm using meteor with vue-meteor, the problem was related to vue-meteor.
I changed the project structure to use separated Vue project in a .client folder and the problem gones. It was not a problema with component, but with vue-meteor package. Anyway, thank you guys.

Vue instance inside Vue instance

Sup people!
I got this HTML code here:
// index.html
<div data-init="component-one">
<...>
<div data-init="component-two">
<button #click="doSomething($event)">
</div>
</div>
This basically references a Vue instance inside another Vue instance if I understood everything correctly. The respective JS code is split up in two files and looks like this:
// componentOne.js
new Vue(
el: '[data-init="component-one"]',
data: {...},
methods: {...}
);
// componentTwo.js
new Vue(
el: '[data-init="component-two"]'
data: {...}
methods: {
doSomething: function(event) {...}
}
);
Now, the problem with this is, that doSomething from componentTwo never gets called.
But when I do some inline stuff, like {{ 3 + 3 }}, it gets computed like it should. So Vue knows there is something. And it also removes the #click element on page load.
I tried fiddling around with inline-template as well, but it doesn't really work as I'd expect it to in this situation. And I figured it isn't meant for this case anyway, so I dropped it again.
What would the correct approach be here? And how can I make this work the easiest way possible with how it's set up right now?
The Vue version we use is 2.1.8.
Cheers!
The problem is that you have two vue instances nested to each other.
If the elements are nested, then you should use the same instance or try components
https://jsfiddle.net/p16y2g16/1/
// componentTwo.js
var item = Vue.component('item',({
name:'item',
template:'<button #click="doSomething($event)">{{ message2 }</button>',
data: function(){
return{
message2: 'ddddddddddd!'
}},
methods: {
doSomething: function(event) {alert('s')}
}
}));
var app = new Vue({
el: '[data-init="component-one"]',
data: {
message: 'Hello Vue!'
}
});
<div data-init="component-one">
<button >{{ message }}</button>
<item></item>
</div>
Separate instances work if they are independant of each other.
as follows:
https://jsfiddle.net/p16y2g16/
var app = new Vue({
el: '[data-init="component-one"]',
data: {
message: 'Hello Vue!'
}
});
// componentTwo.js
var ddd = new Vue({
el: '[data-init="component-two"]',
data: {
message: 'ddddddddddd!'
},
methods: {
doSomething: function(event) {alert('s')}
}
});
But when I do some inline stuff, like {{ 3 + 3 }}, it gets computed like it should. So Vue knows there is something.
Because you have parent instance 'componentOne'. It activated Vue for this template. If you need to set another instance inside, you have to separate part of template. Example (it can lag in snippet!) .
Alternative
https://jsfiddle.net/qh8a8ebg/2/
// componentOne.js
new Vue({
el: '[data-init="component-one"]',
data: {
text: 'first'
},
methods: {}
});
// componentTwo.js
new Vue({
el: '[data-init="component-two"]',
data: {
text: 'second'
},
template: `<button #click="doSomething($event)">{{text}}</button>`,
methods: {
doSomething: function(event) {
console.log(event);
}
}
});
<script src="https://vuejs.org/js/vue.min.js"></script>
<div data-init="component-one">
{{text}}
</div>
<div data-init="component-two">
</div>
The button element inside component-two is referenced as a slot in Vue.
The evaluation of the #click directive value happens in the parent component (component-one, which host component-two). Therefor, you need to declare the click handler over there (over component-one).
If you want the handler to be handled inside component-two, you should declare a click directive for the slot element in it's (component-two) template, and pass the handler function, for instance, as a pop.
good luck.
You're doing everything right except you've nested the 2nd Vue instance inside the 1st. Just put it to the side and it will work as expected.
Vue ignores binding more than once to the same element to avoid infinite loops, which is the only reason it doesn't work nested.
Use vue-cli to create a webpack starter app. vue init app --webpack
Then, try to structure your components this way. Read more: https://v2.vuejs.org/v2/guide/components.html#What-are-Components
This is main.js
import Vue from 'vue'
import ComponentOne from './ComponentOne.vue'
import ComponentTwo from './ComponentTwo.vue'
new Vue({
el: '#app',
template: '<App/>',
components: {
ComponentOne,
ComponentTwo
}
})
This is ComponentOne.vue
<template>
<div class="user">
<div v-for="user in users">
<p>Username: {{ user.username }}</p>
</div>
</div>
</template>
<script>
export default {
data () {
return {
users: [
{username: 'Bryan'},
{username: 'Gwen'},
{username: 'Gabriel'}
]
}
}
}
</script>
This is ComponentTwo.vue
<template>
<div class="two">
Hello World
</div>
</template>
<script>
export default {
}
</script>
<div th:if="${msg.replyFloor}">
<div class="msg-lists-item-left">
<span class="msg-left-edit"
th:classappend=" ${msg.unreadCount == 0} ? 'msg-all-read' ">您在</span>
<span th:text="${msg.topic.title}"
class="msg-left-edit-res"
th:classappend=" ${msg.unreadCount == 0} ? 'msg-all-read' ">问题回答</span>
<span th:text="${msg.type.name}"
class="msg-left-edit "
th:classappend=" ${msg.unreadCount == 0} ? 'msg-all-read' ">帖子相关</span>
<span class="msg-left-edit-number" >
产生了<span th:text="${msg.unreadCount} ? : ${msg.unreadCount} + '条新' : ${msg.unreadCount} + '条' "
th:class="${msg.unreadCount} ? : 'number-inner':''">2132条</span>回复
</span>
</div>
<div class="msg-lists-item-right">
<span th:text="${msg.lastShowTime}">2017-8-10</span>
</div>
</div>

Categories