Custom Select Component on Vue with SVG - vue.js

I'm creating a custom select component on Vue and I'm using tailwind to style it.
I would like to have an chevron-down caret svg align to the right, which will open the select options when clicked. I'm having some trouble with this.
<template>
<div class="flex flex-wrap mb-4 relative">
<select :value="value" #input="$emit('input', $event.target.value)" class="w-full h-12 pl-4 bg-white focus:bg-grey-10 focus:text-grey-5 border-2 border-grey-9 rounded-lg text-sm focus:outline-none">
<option :value="null">
Select an option
</option>
<option v-for="option in options" :value="option.slug">{{ option.name }}</option>
</select>
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 20 20" class="absolute mt-4 mr-4 right-0 cursor-pointer">
<path d="M9.293 12.95l.707.707L15.657 8l-1.414-1.414L10 10.828 5.757 6.586 4.343 8z"/>
</svg>
</div>
</template>
<script>
export default {
props: [ 'value'],
data () {
return {
options: [
{ slug: 'test1', name: 'Test 1' },
{ slug: 'test2', name: 'Test 2' },
],
};
},
}
</script>
This is what it looks like, however the svg when clicked does not open the dropdown.
Any suggestions?

To the <svg> tag, you need to add the .pointer-events-none class. From the Tailwind Docs
Use .pointer-events-none to make an element ignore pointer events. The pointer events will still trigger on child elements and pass-through to elements that are "beneath" the target.
There is a helpful custom select example in the Tailwind docs.

Related

Dynamic Heroicons in Vue.js

I assign the heroicon to a variable from my list of menu items.
const navigation = [{ name: 'Dashboard', icon: TemplateIcon, href: '#', current: true }]
Then I try to display the icon.
<li v-for="item in navigation" class="relative px-6 py-3">
<TemplateIcon class="h-5 w-5" />
<item.icon class="h-5 w-5" />
</li>
The template icon is only shown once, but should be shown twice.
I have already tried this
<{{item.icon}} class="h-5 w-5" />
{{item.icon}}
<svg><path :d="item.icon"></path></svg>
thx for help
You can use Dynamic component approach which is useful to dynamically switch between components.
This can be achieve by using Vue's <component> element with the special :is attribute.
Demo :
new Vue({
el: '#app',
data: {
navigation: [{ icon: 'H3' }]
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<div class="container">
<div v-for="(item, index) in navigation" :key="index">
<component :is="item.icon">Hello</component>
</div>
</div>
</div>
You should use the component component in vue.
You can find the documentation for it here: https://vuejs.org/guide/essentials/component-basics.html#dynamic-components
tldr, pass your component in the is prop
<component :is="item.icon" class="h-5 w-5" />

Algolia vue auto select facets

I am developing an algolia search solution for an ecommerce app with around ~30000 products.
I tried to use the <ais-configure/> component to filter my results before showing them on the page, as I am working with different pages for each category of products and some facet filters also need to be applied if a user was to click on a promotion which would lead to this search result in the code below.
The problem is:
Whenever I use the filters in <ais-configure/> the same filters are not picked up by my <ais-refinement-list/> component which should have the refined facets highlighted.
The below code is not working but descriptive enough for the problem.
Template
<ais-instant-search :search-client.camel="searchClient" index-name="products">
<ais-configure
:filters.camel="searchParameters.filters"
:facet-filters.camel="searchParameters.facetFilters"
/>
<ais-infinite-hits>
<div
slot="loadMore"
slot-scope="{ isLastPage, refineNext }"
class="w-full my-12 px-6 text-sm-black-primary"
>
<button
class="
w-full
h-20
border-4
rounded-md
border-sm-black-primary
font-semibold
text-xl
mb-24
"
:disabled="isLastPage"
#click="refineNext"
>
See more
</button>
</div>
</ais-infinite-hits>
<products-filter-section>
<div v-for="(filterType, id) in filterTypes" :key="id">
<ais-refinement-list :attribute="filterType" searchable show-more>
<template
slot-scope="{
items,
isShowingMore,
isFromSearch,
canToggleShowMore,
refine,
createURL,
toggleShowMore,
searchForItems,
}"
>
<div>
<UtilsFancyTitle
:text="filterType"
text-size="text-lg"
class="ml-4 my-4"
/>
<input
class="bg-white"
#input="searchForItems($event.currentTarget.value)"
/>
<ul class="flex flex-wrap">
<li v-if="isFromSearch && !items.length">No results ...</li>
<li v-for="item in items" :key="item.value" class="my-4 mr-4">
<a
:href="createURL(item)"
:class="
item.isRefined
? 'font-semibold bg-sm-yellow-primary border-sm-yellow-primary '
: 'font-medium'
"
class="
border-4 border-sm-black-primary
py-1.5
px-2
rounded-md
text-sm
"
#click.prevent="refine(item.value)"
>
<ais-highlight attribute="item" :hit="item" />
( {{ item.count.toLocaleString() }} )
</a>
</li>
</ul>
<div class="flex justify-center items-center">
<div class="w-1/4 h-1 bg-sm-black-primary rounded-md"></div>
<button
class="w-2/4 font-semibold my-6"
:disabled="!canToggleShowMore"
#click="toggleShowMore"
>
{{ !isShowingMore ? 'Show more' : 'Hide' }}
</button>
<div class="w-1/4 h-1 bg-sm-black-primary rounded-md"></div>
</div>
</div>
</template>
</ais-refinement-list>
</div>
</products-filter-section>
</ais-instant-search>
Script
import {
AisInstantSearch,
AisRefinementList,
AisInfiniteHits,
AisConfigure,
} from 'vue-instantsearch'
import algoliasearch from 'algoliasearch/lite'
import { mapGetters } from 'vuex'
export default {
components: {
AisInstantSearch,
AisRefinementList,
AisInfiniteHits,
AisConfigure,
},
data() {
return {
searchClient: algoliasearch(
'[API-ENDPOINT]',
'[API-KEY]'
),
filterTypes: [
'type',
'color',
],
searchParameters: {
filters: `category:${this.$route.path.split('/').pop()}`,
facetFilters: `type:someProductType`,
},
}
},
head() {
return {
link: [
{
rel: 'stylesheet',
href: 'https://cdn.jsdelivr.net/npm/instantsearch.css#7.4.5/themes/reset-min.css',
},
],
}
},
}
Is there any working solution for this?
The facets should be auto selected and in their refined state (for example: having their isRefined property set to rue) whenever the algolia query loads and the page renders.
This question has been posted a while ago, so you might already have found an answer, but I'll post mine anyway since I landed here after having the same issue.
In my case, at least, there were 2 problems:
I hadn't configured the "Attributes for faceting". This is explained in the docs and can be done easily via the admin or via API.
I was specifying the facets incorrectly in code. In your example above I think you should have:
searchParameters: {
facetFilters: [[`type:someProductType`]],
},
Note that now facetFilters is an array within an array.
What helps me when I'm unsure I open the admin UI of Algolia, select a search and apply whichever filter I want and then check the raw output. An example from my admin ui:
This gives me an idea of the correct shapes InstantSearch needs.

Vue bootstrap form tags allowing duplicates

I'm trying to allow duplicates in Vue bootstrap's form tags.
I've tried using :tag-validator (in the example) and #tag-state to externally modify the v-model value. However it seems like it is getting rid of the duplicate somewhere. Is this impossible?
Jsfiddle of the example: https://jsfiddle.net/yts54fpd/.
<div id="app">
<template>
<div>
<b-form-group label="Tagged input using select" label-for="tags-component-select">
<!-- Prop `add-on-change` is needed to enable adding tags vie the `change` event -->
<b-form-tags
:tag-validator="tagValidator"
id="tags-component-select"
v-model="value"
size="lg"
class="mb-2"
add-on-change
no-outer-focus
>
<template v-slot="{ tags, inputAttrs, inputHandlers, disabled, removeTag }">
<ul v-if="tags.length > 0" class="list-inline d-inline-block mb-2">
<li v-for="tag in tags" :key="tag" class="list-inline-item">
<b-form-tag
#remove="removeTag(tag)"
:title="tag"
:disabled="disabled"
variant="info"
>{{ tag }}</b-form-tag>
</li>
</ul>
<b-form-select
v-bind="inputAttrs"
v-on="inputHandlers"
:disabled="disabled"
:options="options"
>
<template #first>
<!-- This is required to prevent bugs with Safari -->
<option disabled value="">Choose a tag...</option>
</template>
</b-form-select>
</template>
</b-form-tags>
</b-form-group>
</div>
</template>
</div>
window.onload = () => {
new Vue({
el: '#app',
data() {
return {
options: ['Apple', 'Orange', 'Banana', 'Lime', 'Peach', 'Chocolate', 'Strawberry'],
value: []
}
},
methods: {
tagValidator(tag) {
this.value.push(tag)
return true
}
}
})
}

Add Elements to a Searchable Dropdown That Weren't Originally in the Dropdown - Vue

How can I add an input box into a select element, with Vue?
Let's say I've got a list like ['Red', 'Yellow', 'Green'], and the user wants to choose 'Black' which not in the list.
I want the user to be able to type it, within the select element, get it added to the list, and be selected.
Here is my code so far:
<template>
<div >
<div class="form-group col-md-2">
<label >Colors</label>
<select v-model="selected" class="form-control" >
<option v-for="option in list" class="form-control" :key="option">
{{ option}}
</option>
</select>
</div>
<p>The selected color is: {{selected}}</p>
</div>
</template>
<script>
export default {
data(){
return{
list:['Red', 'Yellow', 'Green'],
selected: '',
};
},
}
</script>
You can use vue-multiselect for the searchable dropdown:
<multiselect
:options="list"
v-model="selected"
></multiselect>
...and add some custom code to add the typed color to list when Enter or Return is pressed:
<div #keyup.enter="addColor($event)">
<multiselect
:options="list"
v-model="selected"
#search-change="typed = $event"
></multiselect>
</div>
addColor() {
if (!this.list.includes(this.typed)) this.list.push(this.typed); // check if inputted color is already in the list; if not, add it; if so, don't
this.selected = this.typed; // set `selected` to the currently typed color
},
Note that you will have to add the CSS for vue-multiselect, which you can see how to do in the installation instructions here.
Sandbox & Full Code

How to make a dynamic Vue navbar?

I have below navbar in Vue. When I pass an array to it it doesn't show the menu.
<template>
<div>
<div class="mt-5 px-2">
<li v-for="item in items" :key="item.name">
<a
href="item.link"
class="group flex items-center px-2 py-2 text-base leading-6 font-medium rounded-md text-white bg-gray-900 focus:outline-none focus:bg-gray-700 transition ease-in-out duration-150"
>
<svg
class="mr-4 h-6 w-6 text-gray-300 group-hover:text-gray-300 group-focus:text-gray-300 transition ease-in-out duration-150"
stroke="currentColor"
fill="none"
viewBox="0 0 24 24"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M3 12l9-9 9 9M5 10v10a1 1 0 001 1h3a1 1 0 001-1v-4a1 1 0 011-1h2a1 1 0 011 1v4a1 1 0 001 1h3a1 1 0 001-1V10M9 21h6"
/>
</svg>
{{ item.name }}
</a>
</li>
</div>
</div>
<!--
items: [
{ name: 'Foo' },
{ link: 'Bar' }
]
-->
</template>
<script>
export default {
name: 'Nav',
props: ['items'],
data() {
return {
parents: [],
};
},
};
</script>
<style scoped></style>
Your items-array you show as example data is unsuitable for the template:
the items-array contains two objects, one with a single property name, the other with a single property link. But based on the template it looks as if name and link should be part of the same object, e.g.:
items: [
{ name: 'Foo1', 'link': 'Bar1' },
{ name: 'Foo2', 'link': 'Bar2' }
]
With the original array passed as by your example, there will likely be an error shown in the browser's console due to item.link not being defined for the first object, and thus probably stopping the rendering process of Vue.
The main problem in your example is that you named your component Nav, but HTML5 already has this element which will cause a collision between your component and HTML's element, just rename into something else.
After you change the name make sure that the array that you pass to that component looks like this - [{name: 'a', link: 'http://a.com'}, {name: 'b', link: 'http://b.com'}].