Bootstrap Vue accordion not expanding with id from array - vue.js

I have an array for a Bootstrap-Vue accordion that includes an id. The button appears with my text but will not expand on clicking. I'm fairly new to coding in general and this is my first week using Vue so super-new to it.
I've tried creating an id of "accordion-1", "accordion-2" etc. and feeding the v-b-toggle and collapse id "accordionItems.accId". I've also tried having the accId be a number (1, 2, etc.) and feeding the v-b-toggle and collapse ids as "'accordion1' + accId" and also +item.id and +"accordionItems.accId. Lastly I tried adding the reactive: true statement because I'm getting the message about reactivity in the Console but nothing works.
<b-card no-body class="mb-1" v-for="item in accordionItems" :key="item.accId">
<b-card-header header-tag="header" class="p-1" role="tab">
<b-button block v-b-toggle="'accordion-' + accordionItems.accId" variant="primary"> {{ item.button }}
</b-button>
</b-card-header>
<b-collapse id="'accordion-' + accordionItems.accId" accordion="my-accordion" role="tabpanel">
<b-card-body>
<b-card-text>{{ item.text }}</b-card-text>
</b-card-body>
</b-collapse>
</b-card>
<script>
export default {
name: "Accordion",
data() {
return { reactive: true,
accordionItems: [
{
accId: 1,
button: `Who played Burt and Heather Gummer in Tremors?`,
text: `Answer: Burt: Michael Gross, Heather: Reba McEntire`
},
{
accId: 2,
button: `In "The Bachelor and the Bobby-Soxer" (1947), what line does Dick Nugent (Cary Grant) use to convince Susan Turner (Shirley Temple) he's no more mature than boys her own age?`,
text: `Answer: "Mellow greetings, yookie dookie!"`
}
],
}
}
}
</script>
When I inspect with Vue, I can see the id is showing as "'accordion-' + accordionItems.accId" so I know I'm missing something. I'm expecting to click on the question and have it expand to show the answer.

You need to "bind" to ID:
<b-collapse :id="'accordion-' + accordionItems.accId" accordion="my-accordion" role="tabpanel">
<b-card-body>
<b-card-text>{{ item.text }}</b-card-text>
</b-card-body>
</b-collapse>
Note the : before id=. This instructs Vue that the value in the quotes is a javascript expression.
What was happening with id="'accordion-' + accordionItems.accId" is that Vue sees the value between the double quotes as a string literal, not a JavaScript Expression.

Related

Has anyone came across this problem? [vue/no-multiple-template-root] The template root disallows 'v-for' directives.eslint-plugin-vue

It is actually a three problems in one:
[vue/no-multiple-template-root]
The template root disallows 'v-for' directives.eslint-plugin-vue
[vue/no-parsing-error]
Parsing error: Expected to be an alias, but got empty.eslint-plugin-vue
[vue/valid-v-for]
Expected 'v-bind:key' directive to use the variables which are defined by the 'v-for' directive.eslint-plugin-vue
Can anyone help me please I am so fed with searching online for it everywhere
enter code
<template>
<div class="post" v-for="post" in posts >
<div><strong>Title</strong>{{post.title}}</div>
<div><strong>Desctiption</strong>{{post.body}}</div>
</div>
</template>
<script>
export default{
data(){
return{
posts:[
{ id: 1, title: 'javascript', body: "the desctiption"},
{ id: 2, title: 'javascript2', body: "the desctiption"},
{ id: 3, title: 'javascript3', body: "the desctiption"},
]
}
}
}
Vue.js must have a single element at the root of the template. If you have av-for directive, when the DOM is populated there will be multiple <div> elements at the root, which Vue does not allow.
So you just need to add another <div> element to surround your v-for div.
Then, move the in posts within your quotes and add a :key
<template>
<div>
<div class="post" v-for="post in posts" :key="post.id">
<div><strong>Title</strong>{{post.title}}</div>
<div><strong>Desctiption</strong>{{post.body}}</div>
</div>
</div>
</template>
In vue two you should one root element, using v-for loop will render multiple elements in the template like :
<div class="post" >
...
</div>
<div class="post" >
...
</div>
to avoid this add an extra div and bind key to the post id :key="post.id":
<template>
<div class="posts">
<div class="post" v-for="post in posts" :key="post.id">
<div><strong>Title</strong>{{post.title}}</div>
<div><strong>Desctiption</strong>{{post.body}}</div>
</div>
</div>
</template>

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.

vuetify datatable search cannot search the value

I found the miss function on vuetify datatable.
I think that my configuration is correct.
This is my datatable vuetify
<v-text-field v-model="search" append-icon="mdi-magnify" label="Search" single-line
hide-details></v-text-field>
</v-card-title>
<v-data-table loading loading-text="Loading... Please wait" :headers="headers"
:items="goodsData" :search="search" :items-per-page="10" class="elevation-1">
<template v-slot:item.typeOfGoods="{ item }">
<div class="userDatatable-inline-title my-3">
<a href="#" class="text-dark fw-500">
<h6 v-if="item.typeOfGoods == 1">Document
#{{item.receiptNumber}}</h6>
<h6 v-if="item.typeOfGoods == 2">Packet
#{{item.receiptNumber}}</h6>
</a>
<p class="pt-1 d-block mb-0">
<i class="fas fa-dolly"></i> via {{item.courier}} • Received at
{{item.created_at}}
</p>
</div>
</template>
<template v-slot:item.receiver="{ item }">
<img class="rounded-circle wh-34"
:src="`/dashboard/img/author/profile/`+item.receiver.avatar"
alt="author">
{{item.receiver.name}}
</template>
<template v-slot:item.status="{ item }">
<span class="media-badge color-white bg-primary" v-if="item.status==0">Available
at
receiptionist</span>
<span class="media-badge color-white bg-success" v-if="item.status==1">Well
received</span>
<span class="media-badge color-white bg-danger"
v-if="item.status==2">Terminated</span>
</template>
<template v-slot:item.actions="{item}">
<a href="#" v-on:click="receivedAction(item.id)" v-if="item.status==0"><span
style="color:blue;"><i class="far fa-check-circle"></i> Update
status</span></a>
<a v-if="item.status==1"><span style="color:green;"><i
class="fas fa-check-circle"></i>
Done</span></a>
</template>
</v-data-table>
This is my script data
// datatable
search: '',
goodsData: [],
headers: [{
text: 'Goods Type',
value: 'typeOfGoods'
}, {
text: 'Recipient',
value: 'receiver'
}, {
text: 'Status',
value: 'status'
}, {
text: 'Actions',
value: 'actions',
filterable: false,
sortable: false
}],
I already read the documentation on codepen and on https://vuetifyjs.com/en/components/data-tables/#api
Or you can see my repo,
https://github.com/bintangjtobing/boxity-app/blob/master/resources/js/components/goodsReceipt.vue
You can see my gif below
Thank you, everyone, hope you can help to solve this prob.
Thank you thank you.
The search only looks in fields you’ve defined. The text “Document” is not in a field, it’s in your template. The number comes from receiptNumber which is not defined in headers or anywhere, it’s just in the template. Therefore the search can’t know about that either.
You’ll need to provide your own filter function as the documentation you linked to explains in the beginning. I don’t see such defined here.
I found a simple solution for this by creating same header and value but hidden for the column used in template.
You can try this:
{ text: "", value: "header_value", align: ' d-none' },
{ text: "Header Name", value: "header_value" }
Wherein you have to hide the other one by using ' d-none' that will be use to still search the value of that column while the other one is use to display the value. Else if you don't want to use this, you can use the other solution stated here, by creating a custom filter/search.

Is there a better way to support label subscripts in dynamically generated b-form-groups with BootstrapVue?

I'm writing a Vue.js app using BootstrapVue. I'm dynamically generating a form based on a simple "field" data structure (see code and screenshot below). The label for each <b-form-group> comes from a label attribute in my field object. It worked fine until I needed a subscript in the label. I'm using the <sub> HTML tag, but v-bind-ing the label treats the HTML as plain text. See the first column of inputs:
Next I tried using <b-form-group>'s label slot instead, using mustache templates. As documented, this also interprets the value as plain text, so I get the same result -- see the second column.
Finally, I was able to achieve the result I wanted using v-html in a span inside the label slot. This works (see the third column), but it seems a bit convoluted, and it's not making the linter happy (yes, I can always turn it off).
Is there a better way to achieve this? I'm relatively new to Vue.js.
(Note that I've simplified the example for brevity, omitting id's, etc.)
<template>
<div class="m-4">
<b-container>
<b-form-row class="round-border">
<b-col
cols="4"
>
<b-form class="mr-4">
<b-form-group
v-for="(field, index) in fields"
:key="index"
:label="field.label"
>
<b-form-input
v-model.number="form[field.model]"
v-bind="field.atts"
/>
</b-form-group>
</b-form>
</b-col>
<b-col
cols="4"
>
<b-form class="mr-4">
<b-form-group
v-for="(field, index) in fields"
:key="index"
>
<label>{{ field.label }}</label>
<b-form-input
v-model.number="form[field.model]"
v-bind="field.atts"
/>
</b-form-group>
</b-form>
</b-col>
<b-col
cols="4"
>
<b-form class="mr-4">
<b-form-group
v-for="(field, index) in fields"
:key="index"
>
<label><span v-html="field.label" /></label>
<b-form-input
v-model.number="form[field.model]"
v-bind="field.atts"
/>
</b-form-group>
</b-form>
</b-col>
</b-form-row>
</b-container>
</div>
</template>
<script>
export default {
components: {},
data () {
return {
form: {
Length: 1,
Width: 4,
Height: 9
},
fields: [
{ label: 'Label<sub>1</sub>', model: 'Length', atts: { type: 'number', step: 1 } },
{ label: 'Label<sub>2</sub>', model: 'Width', atts: { type: 'number', step: 1 } },
{ label: 'Label<sub>3</sub>', model: 'Height', atts: { type: 'number', step: 1 } }
]
};
}
};
</script>
As #Ian Cho says, if the content of field.label property is html as string, the only way to solve it in vue.js is using v-html.
If you are able to change the field item structure to something like {label: 'Label', sub: 1, ...}, you can use label slot as following:
<b-form-group v-for="(f, i) in fields">
<template slot="label">{{f.label}} <sub>{{f.sub}}</sub></template>
<!-- rest of your code -->
</b-form-group>
DEMO: https://jsfiddle.net/4xmr35p0/
If you have to use HTML tags as parameter, v-html is the only way.
If there's another way, please anyone let me know. ;)

How to have a scoped toggle variable in VueJS

I'm coming from an AngularJS background where the ng-repeat has a scoped variables and I'm trying to figure out how to achieve a similar result without the need to create a new component which seems overkill for a lot of situations.
For example:
<div class="item" v-for="item in items">
<div class="title">{{item.title}}</div>
<a #click="showMore = !showMore">Show more</a>
<div class="more" v-if="showMore">
More stuff here
</div>
</div>
In AngularJS that code would work great, but in VueJS if you click on show more it causes the variable to update for every item in the items list, is there anyway to create a local scoped variable inside of the v-for without the need to make a new component?
I was able to get it to work by having the showMore variable be something like #click="showMore = item.id" and then v-if="showMore.id = item.id" but that also seems like too much extra complexity for something that should be much simpler? The other problem with that approach is you can only get one item to show more rather than allow for multiple items to be toggled shown at once.
I also tried changing the items model to include item.showMore but once again that adds more complexity and it causes a problem if you need to update an individual item since the model is changed.
Are there any simpler approaches to this?
What do you think about this: CODEPEN
<template>
<div>
<h1>Items</h1>
<div v-for="item in items"
:key="item.id"
class="item"
>
{{item.name}}
<button #click="show=item.id">
Show More
</button>
<div v-if="item.id == show">
{{item.desc}}
</div>
</div>
</div>
</template>
<script>
export default {
data() {
return {
items: [
{id:1, name:"One", desc: "Details of One"},
{id:2, name:"Two", desc: "Details of Two"},
{id:3, name:"Three", desc: "Details of Three"}
],
show: null
};
}
};
</script>
<style>
.item{
padding: 5px;
}
</style>