Bootstrap Select not working inside Bootstrap Vue Modal - vue.js

I am using Bootstrap Vue with Bootstrap select and the select works perfectly outside the modal
It doesnt open at all inside the modal. Live code is HERE
JS file
Vue.component("suluct", {
template: "#suluct",
props: {
week: [String, Number],
year: [String, Number],
},
mounted() {
const $selectpicker = $(this.$el).find('.selectpicker');
$selectpicker
.selectpicker()
.on('changed.bs.select', () => this.$emit('changeWeek', this.options[$selectpicker.val()]));
},
updated() {
$(this.$el).find('.selectpicker').selectpicker('refresh');
},
destroyed() {
$(this.$el).find('.selectpicker')
.off()
.selectpicker('destroy');
},
computed: {
options() {
// run some logic here to populate options
return [
{
title: "Sunday",
value: "sunday",
}, {
title: "Monday",
value: "monday"
}
]
}
}
})
new Vue({
el: "#app"
})
HTML
<div id="app">
<suluct></suluct>
<div>
<b-btn v-b-modal.modal1>Launch demo modal</b-btn>
<!-- Modal Component -->
<b-modal id="modal1" title="Bootstrap-Vue">
<suluct></suluct>
</b-modal>
</div>
</div>
<script type="text/x-template" id="suluct">
<select class="form-control selectpicker bs-select">
<option
v-for="(option, index) in options"
:key="index"
:value="option.value"
:selected="option.selected">
{{ option.title }}
</option>
</select>
</script>
The dropdown select wont open at all. Any help is appreciated

I had the same problem. After trying various ways, I found a solution.
When you wanna show modals, don not use v-b-modal directive.
Create a method, using this.$bvModal.show() to show modals.
And then you should use this.$nextTick([callback]) .
Final, use javascript to call bootstrap-select in the callback method
The method will be like
Html
<b-btn #click="ShowModal">Launch demo modal</b-btn>
Js
...
ShowModal() {
this.$bvModal.show('modal1');
this.$nextTick(()=>{
$('select').selectpicker();
})
},
...
ps:Sorry for my poor English and hope you can understand what I mean

Related

Vue updating components at the same time after push

I am building a form in Vue
I have a component that looks as follow:
<template>
<transition name="preview-pane">
<label>{{ option.group }}</label>
<input type="text" class="form-control"
:name="`group_name[${index}]`"
v-on:input="option.group = $event.target.value"
:value="option.group">
<a ref="#" class="btn btn-primary float-right" #click="$emit('copy')" role="button">{{ __('Copy') }} </a>
</transition>
</template>
<script>
export default {
props: {
option: {
group: ''
},
index: {}
}
}
</script>
My Vue instance is as follow:
var products = new Vue({
el: '#products',
data: {
options: []
},
methods: {
add() {
this.options.push({
group: ''
})
},
copy(index) {
this.options.push(this.options[index])
}
}
})
And last my html looks as follow
<product-option
v-for="(option, index) in options"
:key="index"
:option="option"
:index="index"
#copy="copy(index)">
</product-option>
I have one button that basically takes one of the options and push it once again (copy method on the vue instance). When I run everything seems fine but then when I change the input it update the props of all the components that have been copied.
What can I do to make vue understand that each component should work separately?
Well in case someone have the same issue, I sort it out like this:
copy(index) {
var object = this.options[index]
var newObject = {}
for (const property in object) {
newObject[property] = object[property]
}
this.options.push(newObject)
}

vuejs semantic ui - drop down not displaying on arrow click

I'm faced with an issue where my semantic drop down in my vue project won't activate when clicking on the arrow icon but works when I click on the rest of the element. The drop down also works when I set the dropdown to activate on hover, but just not on click. Solutions I've tried:
tested if the dynamic id are at fault
tested if the back ticks are confusing things
placed the values directly into the semantic drop down
Aside from the dropdown not activating, the code below works as intended and brings back the selected value to the parent component and can be displayed.
Dropdown.vue:
<template>
<div class="ui selection dropdown" :id="`drop_${dropDownId}`">
<input type="hidden" name="gender" v-model="selected">
<i class="dropdown icon"></i>
<div class="default text">Gender</div>
<div class="menu">
<div class="item" v-for="option in options" v-bind:data-value="option.value">
{{ option.text }}
</div>
</div>
</div>
</template>
<script>
export default {
data: function () {
return {
selected: {}
}
},
watch: {
selected: function (){
this.$emit("dropDownChanged", this.selected)
}
},
props: {
options: Array, //[{text, value}]
dropDownId: String
},
mounted () {
let vm = this;
$(`#drop_${vm.dropDownId}`).dropdown({
onChange: function (value, text, $selectedItem) {
vm.selected = value;
},
forceSelection: false,
selectOnKeydown: false,
showOnFocus: false,
on: "click"
});
}
}
</script>
The component usage:
<vue-drop-down :options="dropDownOptions" dropDownId="drop1" #dropDownChanged="dropDownSelectedValue = $event"></vue-drop-down>
The data in the parent:
dropDownOptions: [
{ text: 'One', value: 'A' },
{ text: 'Two', value: 'B' },
{ text: 'Three', value: 'C' }
],
dropDownSelectedValue: ""
Here is a fiddle of the above but simplified to use a flatter project. However the problem doesn't reproduce :(
https://jsfiddle.net/eywraw8t/210520/
I'm not sure what is causing your issue (as the examples on the Semantic Ui website look similar), but there is a workaround. For you arrow icon:
<i #click="toggleDropDownVisibility" class="dropdown icon"></i>
And then in the methods section of your Vue component:
methods: {
toggleDropDownVisibility () {
$(`#drop_${this.dropDownId}`)
.dropdown('toggle');
}
},

Move elements passed into a component using a slot

I'm just starting out with VueJS and I was trying to port over a simple jQuery read more plugin I had.
I've got everything working except I don't know how to get access to the contents of the slot. What I would like to do is move some elements passed into the slot to right above the div.readmore__wrapper.
Can this be done simply in the template, or am I going to have to do it some other way?
Here's my component so far...
<template>
<div class="readmore">
<!-- SOME ELEMENTS PASSED TO SLOT TO GO HERE! -->
<div class="readmore__wrapper" :class="{ 'active': open }">
<slot></slot>
</div>
Read {{ open ? lessLabel : moreLabel }}
</div>
</template>
<script>
export default {
name: 'read-more',
data() {
return {
open: false,
moreLabel: 'more',
lessLabel: 'less'
};
},
methods: {
toggle() {
this.open = !this.open;
}
},
}
</script>
You can certainly do what you describe. Manipulating the DOM in a component is typically done in the mounted hook. If you expect the content of the slot to be updated at some point, you might need to do the same thing in the updated hook, although in playing with it, simply having some interpolated content change didn't require it.
new Vue({
el: '#app',
components: {
readMore: {
template: '#read-more-template',
data() {
return {
open: false,
moreLabel: 'more',
lessLabel: 'less'
};
},
methods: {
toggle() {
this.open = !this.open;
}
},
mounted() {
const readmoreEl = this.$el.querySelector('.readmore__wrapper');
const firstEl = readmoreEl.querySelector('*');
this.$el.insertBefore(firstEl, readmoreEl);
}
}
}
});
.readmore__wrapper {
display: none;
}
.readmore__wrapper.active {
display: block;
}
<script src="//cdnjs.cloudflare.com/ajax/libs/vue/2.4.2/vue.min.js"></script>
<div id='app'>
Hi there.
<read-more>
<div>First div inside</div>
<div>Another div of content</div>
</read-more>
</div>
<template id="read-more-template">
<div class="readmore">
<!-- SOME ELEMENTS PASSED TO SLOT TO GO HERE! -->
<div class="readmore__wrapper" :class="{ 'active': open }">
<slot></slot>
</div>
Read {{ open ? lessLabel : moreLabel }}
</div>
</template>

Nested Components and Proper Wrapping Techniques in VueJS

I'm trying to put Bootstrap Select2 in a Vue wrapper. Somehow, my code works. But It doesn't seem proper to me.
I found a reference in here https://v2.vuejs.org/v2/examples/select2.html
In the VueJS website example, the el is empty. I didn't stick to this concept where in the new Vue part, they included a template because my el have sidebars and other stuffs also. What I did was I added a
<admin-access></admin-access>
to a section in the HTML.
I think I made my component nested which I'm skeptical if it is proper in VueJS.
Is there a better way to code this?
Templates
<template id="select2-template">
<select>
<slot></slot>
</select>
</template>
<template id="admin-access">
<div>
<transition name="access" enter-active-class="animated slideInRight" leave-active-class="animated slideOutRight" appear>
<div class="box box-solid box-primary" v-if="admin" key="create">
<div class="box-header with-border">
<i class="fa fa-text-width"></i>
<h3 class="box-title">Create Admin</h3>
</div>
<div class="box-body">
<form action="" class="search-form">
<div class="form-group">
<label for="search_user_admin">Search User</label>
<select2 name="search_user_admin" id="search-user-admin" class="form-control select2" :options="options" v-model="selected">
<option disabled value="0">Select one</option>
</select2>
</div>
</form>
</div>
</div>
</transition>
</div>
</template>
Script
Vue.component('admin-access', {
template: '#admin-access',
props: ['options', 'value'],
created: function() {
$('#search-user-admin').select2({
placeholder: "Select User",
allowClear: true
});
},
data: function() {
return {
admin : true,
selected : false
}
},
});
Vue.component('select2', {
template: '#select2-template',
data: function() {
return {
selected: 0,
options: [
{ id: 1, text: 'Hello' },
{ id: 2, text: 'Darkness' },
{ id: 3, text: 'My' },
{ id: 4, text: 'Old' },
{ id: 5, text: 'Friend' }
]
}
},
mounted: function() {
var vm = this;
$(this.$el).select2({
data: this.options,
placeholder: "Select an option",
})
.val(this.value)
.trigger('change')
.on('change', function () {
vm.$emit('input', this.value);
});
},
watch: {
value: function (value) {
$(this.$el).val(value).trigger('change');
},
options: function (options) {
$(this.$el).select2({ data: options });
}
},
destroyed: function () {
$(this.$el).off().select2('destroy');
}
});
var admin = new Vue({
el: '#app'
});
There's nothing wrong with nesting components in the first place. But you have to keep in mind that nesting them, or just in general, just wrapping generic html components like a select into a Vue Component is very costly when it comes to rendering, especially when you are nesting them. Typically when all your component does is just wrapping a simple HTMl element with some styles, you should probably just use generic HTML with classes here, for performance sake.
Also, I'd try to get rid of all the jQuery code inside of Vue Components. Vue offers all the functionality that jQuery has and it'll just increase loading times further.

Vuejs 2.1.10 method passed as prop not a function

I'm very new to Vuejs and JS frameworks in general, so bear with me. I'm trying to call a method that resides in my root component from a child component (2 levels deep) by passing it as a prop, but I get the error:
Uncaught TypeError: this.onChange is not a function
at VueComponent._onChange (category.js:8)
at boundFn (vendor.js?okqp5g:361)
at HTMLInputElement.invoker (vendor.js?okqp5g:2179)
I'm not sure if I'm on the right track by assigning the prop to a method inside the child component, but see what you think:
index.js
var app = new Vue({
el: '#app',
data: function () {
return {
categories: [],
articles: []
}
},
methods: {
onChange: function () {
console.log('first one');
return function () {
console.log('second one');
}
}
},
});
The html:
<div id="app">
<sidebar :onChange=onChange :categories=categories></sidebar>
<varticles :articles=articles></varticles>
</div>
sidebar.js:
Vue.component('sidebar', {
props: ['onChange', 'categories'],
methods: {
_onChange: function () {
this.onChange();
}
},
template: `
<div class="sidebar">
<category v-for="item in categories" :onChange="_onChange" v-bind:category="item"></category>
</div>
`
});
category.js:
Vue.component('category', {
props: ['category', 'onChange'],
methods: {
_onChange: function () {
this.onChange();
}
},
template: `
<div class="category">
<h2>{{ category.name }}</h2>
<ul>
<li v-for="option in category.options">
<input v-on:change="_onChange" v-bind:id="option.tid" type="checkbox" v-model="option.checked">
<label v-bind:for="option.tid">{{ option.name }}</label>
</li>
</ul>
</div>
`
});
There's got to be simpler way to do this!
I'd suggest taking a look at this https://v2.vuejs.org/v2/guide/components.html#camelCase-vs-kebab-case. A simplified version of your code is in this fiddle https://jsfiddle.net/z11fe07p/641/
When writing props in your templates declare them without Capital letters.
A prop declared as onChange in your props is equivalent to on-change in your html.
<sidebar :on-change=onChange :categories=categories></sidebar>
Also I would suggest looking at events and non parent-child communication if you want a link between components that are more than 1 level deep. https://v2.vuejs.org/v2/guide/components.html?#Non-Parent-Child-Communication