Making data reactive with Vue - javascript

I have two components TheCollapsible and TheBuyProduct. The first should be a structural component, making it possible to pass some children and a slot and render multiple collapsible itens (parents), that can expand and show its children. The second (which uses the first), should be a reactive screen to make it possible for the user to add items of some product. The problem here is the quantity of the product is not updated when the add or subtract functions are called.
Notice I have made two trials on reactivity: the first was to add a new property to the prop passed for my second component, this property is my quantity, but it doesn't work; the second trial was to create an array, where the index is the product id and the value is the quantity (which starts at zero). Both fail to be reactive.
I wander if this is because of the way my slot works. In my TheCollapsible component, I need a list of objects that has a property which is a new list (parent has property containing children). But as I said this is merely structural, each children HTML is passed as a slot and every prop I need in the slot I get through the v-slot.
TheCollapsible.vue
<template>
<div class="ui-collapsible">
<div
class="ui-collapsible__parent"
:key="parent_index"
v-for="(parent, parent_index) in parents"
>
<div>
<p>{{ parent.title }}</p>
<button #click="openChildren(parent_index)">
<img
alt="Abrir"
:id="`collapsible-icon-${parent_index}`"
src="/img/icons/chevrons-up-down.svg"
/>
</button>
</div>
<ul
class="ui-collapsible__children"
:id="`collapsible-child-${parent_index}`"
>
<li :key="index" v-for="(child, index) in parent[child_key]">
<slot
:child="child"
:child_index="index"
:parent_index="parent_index"
/>
</li>
</ul>
</div>
</div>
</template>
<script>
export default {
props: ["child_key", "parents"],
methods: {
openChildren(index) {
this.changeIconForOpenOrClosed(index);
const open_child = document.getElementsByClassName(
"ui-collapsible__children--active"
)[0];
if (open_child) {
open_child.classList?.remove("ui-collapsible__children--active");
open_child.classList.add("ui-collapsible__children");
}
const child = document.getElementById(`collapsible-child-${index}`);
if (open_child?.id != child.id) {
child.classList.remove("ui-collapsible__children");
child.classList.add("ui-collapsible__children--active");
}
},
changeIconForOpenOrClosed(index) {
const opened_button = document.querySelector(
'[src$="chevrons-down-up.svg"]'
);
if (opened_button?.src) {
opened_button.src = "/img/icons/chevrons-up-down.svg";
}
const click_button = document.querySelector(`#collapsible-icon-${index}`);
if (click_button?.id != opened_button?.id) {
click_button.src = "/img/icons/chevrons-down-up.svg";
}
},
},
};
</script>
TheBuyProduct.vue
<template>
<div class="row">
<div class="col-6">
<img :alt="product.title" class="product__image" :src="product.image" />
<h1>{{ product.title }}</h1>
<div class="product__lead" v-html="product.lead"></div>
</div>
<div class="col-6">
<h1>Acompanhamentos</h1>
<the-collapsible
child_key="items"
:parents="product.accompaniment_categories"
v-slot="{ child, child_index, parent_index }"
>
<div class="row product__accompaniment">
<img :alt="child.title" class="col-2" :src="child.image" />
<span class="col-4">{{ child.title }}</span>
<div class="col-4 product__quantity">
<button #click="subtract(child_index, parent_index, child.id)">
<i class="fa fa-minus"></i>
</button>
<span>
{{
product.accompaniment_categories[parent_index].items[
child_index
].quantity
}}
-
{{ accompaniments[child.id] }}
</span>
<button #click="add(child_index, parent_index, child.id)">
<i class="fa fa-plus"></i>
</button>
</div>
<span class="col-2">R${{ child.price }}</span>
</div>
</the-collapsible>
<button class="ui-button product__button">Enviar pedido</button>
</div>
</div>
</template>
<script>
export default {
props: ["product"],
data() {
return {
accompaniments: [],
};
},
created() {
this.addPropToAccompaniments();
},
methods: {
addPropToAccompaniments() {
for (let i = 0; i < this.product.accompaniment_categories.length; i++) {
const category_items = this.product.accompaniment_categories[i].items;
for (let j = 0; j < category_items.length; j++) {
category_items[j].quantity = 0;
this.accompaniments[category_items[j].id] = 0;
}
}
},
add(i, j, id) {
this.product.accompaniment_categories[i].items[j].quantity += 1;
console.log("add:", this.accompaniments[id]);
this.accompaniments[id] += 1;
},
subtract(i, j, id) {
this.product.accompaniment_categories[i].items[j].quantity -= 1;
console.log("sub:", this.accompaniments[id]);
this.accompaniments[id] -= 1;
},
},
};
</script>

Related

Dynamic click listeners not working on lwc

I've been trying to get this related list to render on a lightning web component, and when it updates the only thing that doesn't work is the on click listeners... sort of.
Here's the relevant LWC JS
#api recordData;
#api callLogList = [];
#track selectedLog;
#track showAllCallLogs = false;
#track callChainRecordReference;
#track recordError;
#track isLoading = true;
#track multipleCallLogs = false;
connectedCallback() {
this.setup();
this.callChainRecordReference = {
type: 'standard__recordPage',
attributes: {
recordId: this.recordData.Id,
objectApiName: 'Call_Log_Chain__c',
actionName: 'view'
},
};
}
renderedCallback() {
if(!this.callLogList[0].setSelected){
this.setup();
}
}
setup(){
let callHistory = [];
console.log(JSON.parse(JSON.stringify(this.callLogList)));
this.callLogList.forEach(log => {
// Clone the object and set the setSelected function up for each log
let tempLog = {...log,
isSelected: false,
isResolved: log.Status__c === 'Resolved',
setSelected: () => {
let callLogTempList = [];
this.callLogList.forEach(callLog => {
if(log.Id === callLog.Id){
callLogTempList.push({...callLog, isSelected: true});
this.selectedLog = callLog;
} else {
callLogTempList.push({...callLog, isSelected: false});
}
});
this.callLogList = callLogTempList;
}};
if(tempLog.isResolved && !this.isResolved){
let tempRec = {...this.recordData}
tempRec.Status__c = "Resolved";
this.recordData = tempRec;
}
callHistory.push(tempLog);
});
callHistory.sort((date1, date2) => {
return new Date(date1.LastModifiedDate) - new Date(date2.LastModifiedDate);
});
this.callLogList = callHistory;
// Select the last log in the list
this.callLogList[this.callLogList.length - 1].isSelected = true;
this.selectedLog = this.callLogList[this.callLogList.length - 1];
if(this.callLogList.length > 1){
this.multipleCallLogs = true;
}
}
And here's the corresponding HTML template
<!-- Member Call Log Chain Card -->
<template>
<div class="slds-card slds-card_boundary">
<div class="slds-card__header slds-grid slds-m-bottom_small">
<header class="slds-media slds-media_center slds-has-flexi-truncate">
<div class="slds-media__body">
<h2 class="slds-card__header-title">
<span onclick={goToCallChainPage} style="cursor:pointer;color: #0000EE;text-decoration: underline">{Name}</span>
</h2>
</div>
<template if:true={multipleCallLogs}>
<template if:true={showAllCallLogs}>
<div class="slds-no-flex">
<button class="slds-button slds-button_neutral" onclick={showAllCallLogDetails}>Show Selected</button>
</div>
</template>
<template if:false={showAllCallLogs}>
<div class="slds-no-flex">
<button class="slds-button slds-button_neutral" onclick={showAllCallLogDetails}>Show All</button>
</div>
</template>
</template>
</header>
</div>
<div class="slds-card__body slds-card__body_inner">
<div class="slds-grid slds-grid_align-spread slds-m-bottom_small slds-p-bottom_small slds-border_bottom">
<div class="slds-col">
<template iterator:it={callLogList}>
<span key={it.value.Id}>
<template if:true={it.value.isSelected}>
<lightning-badge label={it.value.Name} class="slds-badge_inverse slds-m-bottom_x-small" style="cursor:pointer" onclick={it.value.setSelected}></lightning-badge>
</template>
<template if:false={it.value.isSelected}>
<lightning-badge label={it.value.Name} class="slds-m-bottom_x-small" style="cursor:pointer" onclick={it.value.setSelected}></lightning-badge>
</template>
<template if:false={it.last}>
<lightning-icon class="slds-m-horizontal_x-small" icon-name="utility:forward" size="x-small"></lightning-icon>
</template>
<template if:true={it.last}>
<template if:false={isResolved}>
<lightning-icon class="slds-m-horizontal_x-small" icon-name="utility:forward" size="x-small"></lightning-icon>
<lightning-badge class="slds-badge_lightest slds-m-bottom_x-small" label="Add to Chain" icon-name="utility:add" style="cursor:pointer" onclick={addToCallChain}></lightning-badge>
</template>
</template>
</span>
</template>
</div>
<template if:false={isResolved}>
<template if:true={isOverdue}>
<div class="slds-col">
<lightning-badge label={Status} class="slds-theme_error"></lightning-badge>
</div>
</template>
<template if:false={isOverdue}>
<div class="slds-col">
<lightning-badge label={Status} class="slds-theme_warning"></lightning-badge>
</div>
</template>
</template>
<template if:true={isResolved}>
<div class="slds-col">
<lightning-badge label={Status} class="slds-theme_success"></lightning-badge>
</div>
</template>
</div>
<div class="slds-col">
<template if:true={showAllCallLogs}>
<template for:each={callLogList} for:item="log">
<div key={log.Id} class="slds-border_bottom">
<c-member_-call-log-card record-data={log}></c-member_-call-log-card>
</div>
</template>
</template>
<template if:false={showAllCallLogs}>
<c-member_-call-log-card record-data={selectedLog}></c-member_-call-log-card>
</template>
</div>
</div>
<template if:true={recordError}>
<div class="slds-align_absolute-center slds-text-align_center">
<c-white-glove_-caller-auth-warning-message
warning-header="Error getting Call Log"
warning-message={recordError}>
</c-white-glove_-caller-auth-warning-message>
</div>
</template>
</div>
</template>
At a high level, I just have a function that loops through the Id's and sets the displayed card up. That function gets set on the object itself when the list is passed in from the parent component. (Connected Callback -> this.setup). I'm doing it this way because I can't just pass an ID to the 'setSelected' function onClick since LWC doesn't support parameters in the template. On the initial render, everything works fine. But when I update the list by passing a new one from the parent the only click listener getting set is the last one in the list. I'm crazy lost because it's the exact same code being run from connected to rendered and only connected gives me what I want.
Is there something I'm missing? I've been pouring over the docs and can't find anything that would explain this. Do the click listeners only get set once per connection and then silently fail?

Vue js: load more data button not working properly

In my Vue.js code below I'm trying to add a Show More button to my data coming from API so initially it should show 10 data and whenever clicked load more 10 and so on. I tried answer from:
Load more button in vuejs
but it's not working since I'm looping over an array it gives me the error below can't read property of question title. Is there a way to do it?
<div class="search-askbutton">
<b-row>
<div class="search-wrapper">
<input
type="text"
v-model="search"
placeholder="Search something..."
class="fas fa-search"
/>
</div>
<div class="container vue">
<div v-for="commentIndex in commentsToShow">
<div v-if="commentIndex <= commentsToShow">
<ul
class="container-question"
v-for="(question, index) in filteredList"
:key="index"
>
<div>{{question[commentIndex - 1].questionTitle}} says:</div>
<hr />
</ul>
</div>
</div>
<button #click="commentsToShow += 10">show more</button>
</div>
<script>
export default {
data() {
return { commentsToShow: 10,
search: '',
questions: [],}
},
computed: {
filteredList() {
return this.questions.filter((question) => {
return (
question.questionTitle
.toLowerCase()
.includes(this.search.toLowerCase()) ||
question.owner.username
.toLowerCase()
.includes(this.search.toLowerCase()) ||
question.questionTitle
.toUpperCase()
.includes(this.search.toUpperCase()) ||
question.owner.username
.toUpperCase()
.includes(this.search.toUpperCase())
);
});
},
},
mounted: function() {
questionService.getAllQuestions().then((response) => {
this.questions = response.data.response;}
}
</script>
The problem is your subtraction
<div>{{question[commentIndex - 1].questionTitle}} says:</div>
If commentIndex = 0 then you'll be saying 0-1 = -1 therefore it will not find the -1 index.
You could replace this line
<div v-if="commentIndex <= commentsToShow">
So that it can run only if the index is greater than 0
<div v-if="commentIndex > 0">
1)
v-for returns what's inside an array, not the array itself.
<div>{{question.questionTitle}} says:</div>
2)
also, you can remove the v-for loop.
note:- your reference question is also uses this way.
<div v-for="commentIndex in commentsToShow">
<div v-if="commentIndex <= commentsToShow">
<ul class="container-question">
<div>{{filteredList[commentIndex - 1].questionTitle}} says:</div>
<hr />
</ul>
</div>
</div>

Bind element inside a for loop Vue not working properly

In the following Vue Component I want to loop through dwarfs array. And as long as I am in the current component, everything is fine (TEST) and also all the following properties are correct.
Currenct_Component.vue :
<template>
<div>
<h2>Stamm: {{ tribeName }}</h2>
<div class="card-container">
<div class="card" style="width: 18rem;" v-for="dwarf in dwarfs" :key="dwarf.name">
<!-- TEST -->
<p>{{dwarf}}</p>
<!-- CHILD COMPONENT -->
<app-modal
:showModal="showModal"
:targetDwarf="dwarf"
#close="showModal = false"
#weaponAdded="notifyApp"
/>
<!-- <img class="card-img-top" src="" alt="Card image cap">-->
<div class="card-body">
<h3 class="card-title" ref="dwarfName">{{ dwarf.name }}</h3>
<hr>
<ul class="dwarf-details">
<li><strong>Alter:</strong> {{ dwarf.age }}</li>
<li><strong>Waffen:</strong>
<ul v-for="weapon in dwarf.weapons">
<li><span>Name: {{ weapon.name }} | Magischer Wert: {{ weapon.magicValue }}</span></li>
</ul>
</li>
<li><strong>Powerfactor:</strong> {{ dwarf.weapons.map(weapon => weapon.magicValue).reduce((accumulator, currentValue) => accumulator + currentValue) }}</li>
</ul>
<button class="card-button" #click="showModal = true"><span class="plus-sign">+</span> Waffe</button>
</div>
</div>
</div>
<button id="backBtn" #click="onClick">Zurück</button>
</div>
</template>
<script>
import Modal from './NewWeaponModal.vue';
export default {
data() {
return {
showModal: false,
}
},
components: { appModal : Modal },
props: ['tribeName', 'dwarfs'],
methods: {
onClick() {
this.$emit('backBtn')
},
notifyApp() {
this.showModal = false;
this.$emit('weaponAdded');
}
},
}
</script>
But when I bind the element dwarf to the Child Component <app-modal/> it changes to the next dwarf in the array dwarfs (TEST) - (So as the result when i add a new weapon in the modal-form it gets added to the second dwarf...):
Child_Component.vue :
<template>
<div>
<div class="myModal" v-show="showModal">
<div class="modal-content">
<span #click="$emit('close')" class="close">×</span>
<h3>Neue Waffe</h3>
<!-- TEST -->
<p>{{ targetDwarf }}</p>
<form>
<input
type="text"
placeholder="Name..."
v-model="weaponName"
required
/>
<input
type="number"
placeholder="Magischer Wert..."
v-model="magicValue"
required
/>
<button #click.prevent="onClick">bestätigen</button>
</form>
</div>
</div>
</div>
</template>
<script>
export default {
data() {
return {
weaponName: '',
magicValue: '',
}
},
props: ['showModal', 'targetDwarf'],
methods: {
onClick() {
if(this.weaponName !== '' &&
Number.isInteger(+this.magicValue)) {
let newData = {...this.dwarf};
newData['weapons'] = [
...this.dwarf['weapons'],
{
"name": this.weaponName,
"magicValue": Number.parseInt(this.magicValue)
},
];
this.$http.post("https://localhost:5019/api", newData)
.then(data => data.text())
.then(text => console.log(text))
.catch(err => console.log(err));
this.$emit('weaponAdded');
} else {
alert('You should fill all fields validly')
}
},
}
}
</script>
It looks like you have the <app-modal/> component inside of the v-for="dwarf in dwarfs" loop, but then the control for showing all of the modal components created by that loop is just in one variable: showModal. So when showModal is true, the modal will show each of the dwarfs, and I'm guessing the second dwarf's modal is just covering up the first one's.
To fix this, you could move the <app-modal /> outside of that v-for loop, so there's only one instance on the page, then as part of the logic that shows the modal, populate the props of the modal with the correct dwarf's info.
Something like this:
<div class="card-container">
<div class="card" v-for="dwarf in dwarfs" :key="dwarf.name">
<p>{{dwarf}}</p>
<div class="card-body">
<button
class="card-button"
#click="() => setModalDwarf(dwarf)"
>
Waffe
</button>
</div>
</div>
<!-- Move outside of v-for loop -->
<app-modal
:showModal="!!modalDwarfId"
:targetDwarf="modalDwarfId"
#close="modalDwarfId = null"
#weaponAdded="onDwarfWeaponAdd"
/>
</div>
export default {
//....
data: () => ({
modalDwarfId: null,
)},
methods: {
setModalDwarf(dwarf) {
this.modalDwarfId = drawf.id;
},
onDwarfWeaponAdd() {
//...
}
},
}
You could then grab the correct dwarf data within the modal, from the ID passed as a prop, or pass in more granular data to the modal so it's more "dumb", which is the better practice so that the component isn't dependent on a specific data structure. Hope that helps
Courtesy of #Joe Dalton's answer, a bit alternated for my case:
<div class="card" style="width: 18rem;" v-for="dwarf in dwarfs" :key="dwarf.name">
...
<button class="card-button" #click="setModalDwarf(dwarf)"><span class="plus-sign">+</span> Waffe</button>
<div>
<app-modal
:showModal="showModal"
:targetDwarf="currentDwarf"
#close="showModal = false"
#weaponAdded="notifyApp"
/>
<script>
import Modal from './NewWeaponModal.vue';
export default {
data() {
return {
showModal: false,
currentDwarf: null,
}
},
components: { appModal : Modal },
props: ['tribeName', 'dwarfs'],
methods: {
setModalDwarf(dwarf) {
this.currentDwarf = dwarf;
this.showModal = true;
},
...
}
</script>

Array Splice always delete an item from last?

I am facing a problem in deleting item from an array. Array splice supposed to work but its not working like I want. Its always delete the item from last. I am using Vue.js . I am pushing item dynamically to an array. But after click remove its delete from the last. why I am facing this. I am attaching the codes.
<template>
<div>
<span class="badge badge-pill mb-10 px-10 py-5 btn-add" :class="btnClass" #click="addBtn"><i class="fa fa-plus mr-5"></i>Button</span>
<div class="block-content block-content-full block-content-sm bg-body-light font-size-sm" v-if="buttons.length > 0">
<div v-for="(item, index) in buttons">
<div class="field-button">
<div class="delete_btn"><i #click="remove(index)" class="fa fa-trash-o"></i></div>
<flow-button v-model="item.title" :showLabel="false" className="btn btn-block min-width-125 mb-10 btn-border" mainWrapperClass="mb-0" wrapperClass="pt-0" placeholder="Button Title"></flow-button>
</div>
</div>
</div>
</div>
</div>
</template>
<script>
import flowButton from '../assets/flow-button'
export default {
name: "textArea",
props:{
index : Number
},
data() {
return {
buttons : [],
btnClass : 'badge-primary',
}
}
components : {
flowButton
},
methods : {
addBtn () {
if(this.buttons.length >= 2) {
this.btnClass = 'btn-secondary'
}
if(this.buttons.length < 3) {
this.buttons.push({
title : ''
});
}
},
remove(index) {
this.buttons.splice(index, 1)
}
}
}
</script>
This must be because of your flow-button I have tried to replicate your error but endup to this code. I just replaced the flow-button with input and it works. Try the code below.
Use v-bind:key="index", When Vue is updating a list of elements rendered with v-for, by default it uses an “in-place patch” strategy. If the order of the data items has changed, instead of moving the DOM elements to match the order of the items, Vue will patch each element in-place and make sure it reflects what should be rendered at that particular index. This is similar to the behavior of track-by="$index"
You have missing comma between data and components, I remove the component here it won't cause any error now, and more tips don't mixed double quotes with single qoutes.
<template>
<div>
<span class="badge badge-pill mb-10 px-10 py-5 btn-add" :class="btnClass" #click="addBtn"><i class="fa fa-plus mr-5"></i>Button</span>
<div class="block-content block-content-full block-content-sm bg-body-light font-size-sm" v-if="buttons.length > 0">
<div v-for="(item, index) in buttons" v-bind:key="index">
<div class="field-button">
<div class="delete_btn"><i #click="remove(index)" class="fa fa-trash-o">sdfsdff</i></div>
<input type="text" v-model="item.title" :showLabel="false" className="btn btn-block min-width-125 mb-10 btn-border" mainWrapperClass="mb-0" wrapperClass="pt-0" placeholder="Button Title"/>
</div>
</div>
</div>
</div>
</template>
<script>
export default {
name: 'textArea',
props: {
index: Number
},
data () {
return {
buttons: [],
btnClass: 'badge-primary'
}
},
methods: {
addBtn () {
if (this.buttons.length >= 2) {
this.btnClass = 'btn-secondary'
}
if (this.buttons.length < 3) {
this.buttons.push({
title: ''
})
}
},
remove (index) {
this.buttons.splice(index, 1)
}
}
}
</script>
I think that you may be facing a conflict with the index prop of your component. Try to use a different name for the index of your v-for loop:
<div v-for="(item, ind) in buttons">
<div class="field-button">
<div class="delete_btn"><i #click="remove(ind)" class="fa fa-trash-o"></i></div>
<flow-button v-model="item.title" :showLabel="false" className="btn btn-block min-width-125 mb-10 btn-border" mainWrapperClass="mb-0" wrapperClass="pt-0" placeholder="Button Title"></flow-button>
</div>
</div>
Try this. Removing an item correctly using this.
<div v-for="(item, ind) in buttons" :key="JSON.stringify(item)">

Vuejs - Accordion

I'm trying to create an accordion using vuejs.
I found some examples online, but what I want is different. For SEO purpose I use "is" and "inline-template", so the accordion is kind of static not fully created in Vuejs.
I have 2 problems/questions:
1) I need to add a class "is-active" on the component based on user interaction(clicks), because of this I receive the following error.
Property or method "contentVisible" is not defined on the instance but
referenced during render. Make sure to declare reactive data
properties in the data option.
This probable because I need to set it at instance level. But "contentVisible" have a value (true or false) different for each component.
So I thought using at instance level an array of "contentVisible" and a props (pass thru instance) and custom events on child to update the instance values.
2) Could work but it is a static array. How can I make a dynamic array (not knowing the number of item components) ?
<div class="accordion">
<div>
<div class="accordion-item" is="item" inline-template :class="{ 'is-active': contentVisible}" >
<div>
<a #click="toggle" class="accordion-title"> Title A1</a>
<div v-show="contentVisible" class="accordion-content">albatros</div>
</div>
</div>
<div class="accordion-item" is="item" inline-template :class="{ 'is-active': contentVisible}" >
<div>
<a #click="toggle" class="accordion-title"> Title A2</a>
<div v-show="contentVisible" class="accordion-content">lorem ipsum</div>
</div>
</div>
</div>
var item = {
data: function() {
return {
contentVisible: true
}
},
methods: {
toggle: function(){
this.contentVisible = !this.contentVisible
}
}
}
new Vue({
el:'.accordion',
components: {
'item': item
}
})
Update
I create the following code but the custom event to send the modification from component to instance is not working, tabsactive is not changing
var item = {
props: ['active'],
data: function() {
return {
contentVisible: false
}
},
methods: {
toggle: function(index){
this.contentVisible = !this.contentVisible;
this.active[index] = this.contentVisible;
**this.$emit('tabisactive', this.active);**
console.log(this.active);
}
}
}
new Vue({
el:'.accordion',
data: {
tabsactive: [false, false]
},
components: {
'item': item
}
})
<div class="accordion" **#tabisactive="tabsactive = $event"**>
<div class="accordion-item" is="item" inline-template :active="tabsactive" :class="{'is-active': tabsactive[0]}">
<div>
<a #click="toggle(0)" class="accordion-title"> Title A1</a>
<div v-show="contentVisible" class="accordion-content">albatros</div>
</div>
</div>
<div class="accordion-item" is="item" inline-template :active="tabsactive" :class="{'is-active': tabsactive[1]}">
<div>
<a #click="toggle(1)" class="accordion-title" > Title A2</a>
<div v-show="contentVisible" class="accordion-content">lorem ipsum</div>
</div>
</div>
</div>
This works for me:
<template>
<div>
<ul>
<li v-for="index in list" :key="index._id">
<button #click="contentVisible === index._id ? contentVisible = false : contentVisible = index._id">{{ index.title }}</button>
<p v-if='contentVisible === index._id'>{{ index.item }}</p>
</li>
</ul>
</div>
</template>
<script>
export default {
name: "sameName",
data() {
return {
contentVisible: false,
list: [
{
_id: id1,
title: title1,
item: item1
},
{
_id: id2,
title: title2,
item: item2
}
]
};
},
};
</script>
On point 1:
You have to define contentVisible as a vue instance variable, as you have accessed it with vue directive v-show, it searches this in vue data, watchers, methods, etc, and if it does not find any reference, it throws this error.
As your accordion element is associated with the parent component, you may have to add contentVisible data there, like following:
new Vue({
el:'.accordion',
data: {
contentVisible: true
}
components: {
'item': item
}
})
If you have multiple items, you may use some other technique to show one of them, like have a data variable visibleItemIndex which can change from 1 to n-1, where n is number of items.
In that case, you will have v-show="visibleItemIndex == currentIndex" in the HTML.
You can as well have hash for saving which index are to de displayed and which to be collapsed.
On point 2:
You can use v-for if you have dynamic arrays. you can see the documentation here.
I'm having a real hard time understanding what exactly it is you want or why you would want it, but I think this does it?
Vue.component('accordion-item', {
template: '#accordion-item',
methods: {
toggle() {
if(this.contentVisible){
return
}
if(this.$parent.activeTab.length >= 2){
this.$parent.activeTab.shift()
}
this.$parent.activeTab.push(this)
}
},
computed: {
contentVisible() {
return this.$parent.activeTab.some(c => c === this)
}
}
})
const Accordion = Vue.extend({
data() {
return {
activeTab: []
}
},
methods: {
handleToggle($event) {
this.activeTab = []
}
}
})
document.querySelectorAll('.accordion').forEach(el => new Accordion().$mount(el))
<script src="https://unpkg.com/vue/dist/vue.min.js"></script>
<template id="accordion-item">
<div class="accordion-item" :class="{ 'is-active': contentVisible}">
<slot name="title"></slot>
<div v-show="contentVisible" class="accordion-content" #click="$emit('toggle', $event)">
<slot name="content"></slot>
</div>
</div>
</template>
<div class="accordion">
<accordion-item #toggle="handleToggle">
<p slot="title">a title</p>
<p slot="content">there are words here</p>
</accordion-item>
<accordion-item #toggle="handleToggle">
<p slot="title">titles are for clicking</p>
<p slot="content">you can also click on the words</p>
</accordion-item>
<accordion-item #toggle="handleToggle">
<p slot="title">and another</p>
<p slot="content">only two open at a time!</p>
</accordion-item>
<accordion-item #toggle="handleToggle">
<p slot="title">and #4</p>
<p slot="content">amazing</p>
</accordion-item>
</div>

Categories

Resources