Nested Components and Proper Wrapping Techniques in VueJS - vue.js

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.

Related

Bootstrap Select not working inside Bootstrap Vue Modal

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

How to use query parameter in Vue search box?

I have a page with a search box on it using Vue. What I want to do is this: when a user comes from another page with a parameter in the URL (e.g., myurl.com/?windows), I capture the parameter and populate the search field to run the search on that string when the page loads. If there's no parameter, nothing happens.
I'm capturing the string from the URL with JavaScript, but don't see how to get it in the input to run the search.... I created a method but don't see how to apply it.
<div id="app">
<input type="text" v-model="search" placeholder="Search Articles" />
<div v-for="article in filteredArticles" v-bind:key="article.id" class="container-fluid to-edges section1">
<div class="row">
<div class="col-md-4 col-sm-12 section0">
<div class="section0">
<a v-bind:href="article.url" v-bind:title="toUppercase(article.title)">
<img class="resp-img expand section0"
v-bind:src="article.src"
v-bind:alt="article.alt"/>
</a>
</div>
<div>
<h3 class="title-sec">{{ article.title }}</h3>
<p>{{ article.description }}</p>
</div>
</div>
</div>
</div>
</div>
<script type="text/javascript">
var pgURL = window.location.href;
var newURL = pgURL.split("?")[1];
console.log(newURL);
</script>
// Filters
Vue.filter('to-uppercase', function(value){
return value.toUpperCase();
});
new Vue({
el: "#app",
data: {
articles: [
{ id: 1, title: 'Trend Alert: Black Windows', category: 'Windows', description: 'Timeless, elegant, and universally flattering, black is an excellent color to add to any wardrobe – or any window. Get in the black with this chic design trend.', src: 'http://i1.adis.ws/i/stock/Trending_Polaroid_Black_Windows_2018_1?$trending-mobile$', url: '/{StorefrontContextRoot}/s/trending/trend-alert-black-windows', alt: 'Pantone Colors image' },
{ id: 2, title: 'Benefits of a Pass-Through Window', category: 'Windows', description: 'Whether you’re adding a pass-through window in order to enjoy an al fresco aperitif or for easier access to appetizers in the kitchen, we’re big fans of bringing the outdoors in.', src: 'http://i1.adis.ws/i/stock/polaroid_benefitsofapassthroughwindow655x536?$trending-mobile$', url: '/{StorefrontContextRoot}/s/trending/kitchen-pass-through-bar-window', alt: 'Benefits of a Pass-Through Window image' }, etc....
],
search: ''
},
methods: {
toUppercase: function(title){
return title.toUpperCase();
},
urlSearch: function(newURL) {
if (newURL) {
return this.search = newURL;
}
}
},
computed: {
filteredArticles: function() {
// returning updated array based on search term
return this.articles.filter((article) => {
return article.category.match(new RegExp(this.search, "i"));
});
}
}
})
You can call the urlSearch method during the mounted hook:
mounted() {
this.urlSearch(newURL)
},
methods: {
urlSearch(url) {
return this.search = url
}
},

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');
}
},

Vue.js reactivity inner mechanisms

Given the following Vue code, how does it know to render the v-for again when selected is changed?
It makes complete sense to me when todos is changed.
So, Vue notices that there's a method isSelected involved and then uses "reflection" to watch the selected value, since it's an instance value?
Is that what's happening under the hood?
<div id="app">
<ol>
<li v-for="todo in todos" :class="{ 'selected': isSelected(todo.text) }">
{{ todo.text }}
</li>
</ol>
</div>
<script>
var app = new Vue({
el: '#app',
data: {
todos: [
{ text: 'foo' },
{ text: 'bar' },
{ text: 'quz' }
],
'selected': 'bar'
},
methods: {
isSelected: function(text) {
return text != this.selected;
}
}
})
app.todos.push({ text: 'test' });
app.todos[0].text = 'change';
app.selected = 'foo';
</script>

Using a vue-apollo component with different queries on the same page

I would like to make a wrapper component using Apollo, which receives different GraphQL queries and variables within props, queries GraphQL source and and passes response to its child component. I would like to use this component twice on a page with different queries.
But I think I'm stuck at some point. I end up having both instances of the component working with same inputs: query prop of the first dropdown component is being used in all dropdowns on the page. Although they have a different scope, different keywords and items, all dropdowns on the page are using the query of first dropdown.
Here is my DropdownSearch component. It passes keyword from searchbar component to query-list component:
<template>
<div class="dropdown-search">
<div class="dropdown-search-display" #click="toggleDropdown">
<span>{{value[nameProp]}}</span>
<span class="dropdown-search-dropdown-toggle">
<i v-if="!isOpen" class="fa fa-caret-down" aria-hidden="true"></i>
<i v-if="isOpen" class="fa fa-caret-up" aria-hidden="true"></i>
</span>
</div>
<div :class="{ 'dropdown-search-dropdown': true, 'dropdown-search-dropdown--open': isOpen}" >
<search-bar v-model="searchKeyword" class="dropdown-search-searchbar"></search-bar>
<div class="dropdown-search-list">
<query-list
:query="query"
:keyword="searchKeyword"
:listItemComponent="ListItem"
></query-list>
</div>
</div>
</div>
</template>
<script>
const ListItem = {
template: '<div class="dropdown-search-list-item">{{record.name}}</div>',
props: {
record: { required: true },
},
};
export default {
name: 'dropdownSearch',
props: {
name: String,
nameProp: String,
value: Object,
query: Object,
},
components: {
'dropdown-list-item': ListItem,
},
data() {
return {
isOpen: false,
searchKeyword: '',
ListItem,
};
},
methods: {
toggleDropdown() {
this.isOpen = !this.isOpen;
},
},
};
</script>
QueryList component, which is using Apollo to make queries and displays a list of results:
<template>
<div class="">
<loading-indicator :isLoading="loading > 0"></loading-indicator>
<ul class="app__list">
<li class="app__list-item" v-for="item in items" :key="item.id">
<component
:is="listItemComponent"
:record="item"
></component>
</li>
</ul>
</div>
</template>
<script>
export default {
name: 'queryList',
props: {
query: Object,
keyword: String,
listItemComponent: { required: true },
},
data() {
return {
items: [],
loading: 0,
};
},
apollo: {
items: {
query() {
return this.query;
},
variables() {
return {
keyword: this.keyword || '',
};
},
loadingKey: 'loading',
},
},
};
</script>
This is how I use Dropdown Search components on a page:
<label class="edit-record-field">
<span class="edit-record-field-label">Category</span>
<dropdown-search
class="edit-record-field-input"
v-model="record.category"
:nameProp="'name'"
:query="getCategories"
></dropdown-search>
</label>
<label class="edit-record-field">
<span class="edit-record-field-label">Location</span>
<dropdown-search
class="edit-record-field-input"
v-model="record.location"
:nameProp="'name'"
:query="getLocation"
></dropdown-search>
</label>
I'm looking for a solution. I would be grateful if you could help me to make my components working.