Can't add Object to an Array - vue.js

I'm having an empty array for my breadcrumb and wanna fill it with every step i take in my category tree.
But as i said in Title, it won't add or to be more precise, it add but won't show!!
it wont show on my template! it wont show on console! only when i console.log("bread", Array.from(this.breadcrumbs)) it shows in console.
how can i fill my this.breadcrumbs with the category obj that i send trough an event!!
my category structure is like this: {id: 1, title: "Cat1"}
here is the code of page:
my main problem is that i'm checking if the breadcrumbs has length show it, but since i get empty arr even after push, my breadcrumb section just show the all.
Update:
i removed the if statements in my template, breadcrumb section and i when i click a cat it's title will be shown on breadcrumb but length and array still empty even as trying to display on template!! ( {{breadcrumb}} and {{breadcrumb.length}} are []and0` ).
BTW i'm on Nuxtjs 2.13
<template>
<div class="ma-4">
<!-- Breadcrumb -->
<div class="d-flex">
<template v-if="breadcrumbs.length">
<span role="button" #click.prevent="getAll()">{{lang.all}} > </span>
<span role="button" v-for="(br, index) in newBreadCrumb" :key="br.id" #click.prevent="goBread(br, index)">{{br.title}} > </span>
<span>{{breadcrumbs[breadcrumbs.length - 1]}}</span>
</template>
<template v-else>
<span>{{lang.all}}</span>
</template>
</div>
<!--btn add-->
<addcatbtn v-if="showBtn" #clicked="addNewCat()" />
<!--cards-->
<categorylistcard v-for="(category, index) in cmsCat"
:key="category.id"
:cat="category"
:index="index"
#addnewsub="addNewCat()"
/>
<!-- dialog -->
<v-dialog v-model="dialog" persistent max-width="600px">
<v-card>
<v-card-title>
<span class="headline" v-if="editId">{{lang.edit}} </span>
<span class="headline" v-else>{{lang.addcat}} </span>
</v-card-title>
<v-card-text>
<v-container grid-list-md>
<v-layout wrap>
<v-flex xs12>
<v-text-field :label="lang.title" outlined v-model="title"></v-text-field>
</v-flex>
</v-layout>
</v-container>
</v-card-text>
<v-card-actions>
<v-spacer></v-spacer>
<v-btn class="text_main_color theme__cta__color px-5" #click="closeDialog()">{{lang.close}}</v-btn>
<v-btn class="text_main_color theme__btn__s px-5" #click="insertData()">{{lang.save}}</v-btn>
</v-card-actions>
</v-card>
</v-dialog>
</div>
</template>
<style>
</style>
<script>
import addcatbtn from '~/components/global/cms/addcatbtn'
const categorylistcard = ()=>import('~/components/' + process.env.SITE_DIRECTION + '/cms/categorylistcard')
export default {
layout:'cms',
components:{
'categorylistcard': categorylistcard,
'addcatbtn': addcatbtn
},
data(){
return{
dialog: false,
editId: 0,
newId: 0,
title: null,
catList: [],
editIndex: 0,
parentId: 0,
editSubCat: false,
showBtn: true,
onSubCat: false,
breadcrumbs: []
}
},
methods:{
addNewCat(){
this.dialog = true
this.title = null
},
closeDialog(){
this.dialog = false
this.title = null
this.editId = 0
this.editIndex = 0
},
getAll(){
this.$fetch()
this.showBtn = true
this.onSubCat = false
this.breadcrumbs = []
},
goBread(cat, index){
this.breadcrumbs.lenght = index
$nuxt.$emit('gobread',cat)
},
async insertData(){
if(this.notEmpty(this.title)){
if(this.editId){
let response = await this.axiosGet(`category/update/${this.editId}/${this.title}`)
if(this.resOk(response.status)){
const data = {index: this.editIndex , title: this.title}
if(this.editSubCat){
this.updateCmsSubCat(data)
}else{
this.updateCmsCat(data)
}
$nuxt.$emit('editedcat', {id: this.editId, title: this.title})
this.closeDialog()
}
}else{
if(this.onSubCat){
let response = await this.axiosGet(`category/insertsub/${this.newId}/${this.title}`)
if(this.resOk(response.status)){
// TODO: must get the id from response!!
console.log('insertsub')
const data = {id: this.newId*2 , title: this.title}
this.addCmsSubCat(data)
this.closeDialog()
}
}else{
let response = await this.axiosGet(`category/insert/${this.title}`)
if(this.resOk(response.status)){
// TODO: must get the id from response!!
const data = {id: 34 , title: this.title}
this.addCmsCat(data)
this.closeDialog()
}
}
}
}
}
},
async fetch(){
let response = await this.axiosGet(`categories/admin/0/1`)
if(this.notEmpty(response.data)){
this.setCmsCat(response.data.items)
}
},
computed:{
newBreadCrumb(){
let x = this.breadcrumbs
return x.splice(this.breadcrumbs.lenght-1,1)
}
},
created(){
this.$nuxt.$on('deletecat', (index)=>{
this.removeCmsCat(index)
})
this.$nuxt.$on('editcat', (category, index)=>{
this.title = category.title
this.editId = category.id
this.editIndex = index
this.dialog = true
})
this.$nuxt.$on('setparid', (id)=>{
this.parentId = id
})
this.$nuxt.$on('editsub', ()=>{
this.editSubCat = true
})
this.$nuxt.$on('showsub', (cat)=>{
this.newId = cat.id
this.showBtn = !this.showBtn
this.onSubCat = !this.onSubCat
})
this.$nuxt.$on('addbreadcrumb', (category)=>{
this.breadcrumbs.push(category) // category:{id: 1, title: "Cat1"}
console.log('cat: ')
console.log(category) // get the obj
console.log('bread: ')
console.log(this.breadcrumbs) // get empty array
})
}
}
</script>

In your computed, you are calling splice. That mutates the object. A big no-no in vue is to mutate your state from a computed property.
Try to create a new object and return it, by calling slice first:
const copy = x.slice()
copy.splice(this.breadcrumbs.length-1,1)
return copy
Also, you have a typo lenght instead of length

Related

Is there any way to pass props to component without calling it

I have 3 components like: Parent, First-child and Second-child. And I am iterating First-child in Parent component in array(it is cards), and I want to call Second-child in Parent component with First-child's props(props of one card).
My Parent component looks like this(how I am calling First-child):
``
<CardComponent
v-for="card of cards"
:key="card.urlsId"
:cardImages="card.images"
:cardTitle="card.title"
:cardDescription="card.description"
:mediaRef="card.urlsId"
:dbRef="card.dbId"
:deleteBtn="true"
:imagesWithSlider="true"
/>
And my First child is:
<template>
<div class="cards">
<v-card class="card-container">
<div class="delete-btn">
<v-btn
v-if="deleteBtn"
class="mx-2"
fab
dark
small
#click="$emit('onOpenDeleteModal')"
>
<v-icon dark> mdi-delete </v-icon>
</v-btn>
</div>
<ImageSlider
v-if="imagesWithSlider"
:imagesArray="cardImages"
:arrowBtns="false"
/>
<div class="text-container">
<h3 class="card-title">{{ cardTitle }}</h3>
<p class="card-description">{{ cardDescription }}</p>
</div>
</v-card>
</div>
</template>
<script>
export default {
props: {
cardImages: {
type: Array,
default: null,
},
cardTitle: {
type: String,
default: 'Title',
},
cardDescription: {
type: String,
default: 'Description',
},
deleteBtn: {
type: Boolean,
},
imagesWithSlider: {
type: Boolean,
},
mediaRef: {
type: String,
default: '',
},
dbRef: {
type: String,
default: '',
},
deleteModalOpen: {
type: Boolean,
},
},
emits: ['onOpenDeleteModal', 'onCloseDeleteModal'],
}
</script>
And my Second-child is:
<template>
<v-card class="modal" :loading="newCard.loading ? true : false">
<v-card class="modal-header">
<h3 v-if="addCardModal" class="header-title">New card</h3>
<h3 v-if="deleteModal" class="header-title">Delete</h3>
<v-icon aria-hidden="false" width="100%" #click="$emit('closeModal')"
>mdi-close</v-icon
>
</v-card>
<!-- Delete Modal -->
<div v-if="deleteModal" class="modal-delete">
<h3>Are you really want to delete this card ?</h3>
<div class="modal-delete-btns">
<v-btn #click="$emit('closeModal')">Cancel</v-btn>
<v-btn color="error" #click="$emit('onDeleteCard')">Delete</v-btn>
</div>
</div>
<!-- Add New Card Modal -->
<form
v-if="addCardModal"
class="modal-container"
#submit.prevent="postNewCardToDb"
>
<v-file-input
v-model="newCard.cardImages"
:clearable="false"
multiple
show-size
label="Upload card images"
#change="previewImage"
>
</v-file-input>
<v-file-input
v-model="newCard.cardVideo"
:clearable="false"
show-size
label="Upload video"
>
</v-file-input>
<div v-if="newCard.cardImageUrls.length !== 0" class="preview-image">
<ImageSlider :imagesArray="newCard.cardImageUrls" :arrowBtns="true" />
</div>
<v-text-field
v-model="newCard.cardTitle"
label="Enter card title"
></v-text-field>
<v-text-field
v-model="newCard.cardSnippet"
label="Enter card description"
></v-text-field>
<v-btn type="submit" :loading="newCard.loading ? true : false" block
>POST</v-btn
>
</form>
</v-card>
</template>
<script>
import { v4 as uuidV4, v1 as uuidV1 } from 'uuid'
export default {
/* eslint-disable no-console */
props: {
addCardModal: {
type: Boolean,
},
deleteModal: {
type: Boolean,
},
},
emits: ['closeModal', 'onDeleteCard'],
data() {
return {
newCard: {
loading: false,
cardImages: [],
cardVideo: null,
cardImageUrls: [],
cardTitle: '',
cardSnippet: '',
},
}
},
methods: {
previewImage($event) {
for (const image of event.target.files) {
this.newCard.cardImageUrls.push(URL.createObjectURL(image))
}
},
async getMediaUrlsFromStorage(newCardData) {
const cardMediaRef = uuidV1()
const cardImagesRef = await this.$fire.storage
.ref('/albums_cards/')
.child(cardMediaRef)
const videoRef = await cardImagesRef.child(uuidV4())
if (this.newCard.cardVideo) {
await videoRef.put(this.newCard.cardVideo)
const videoUrl = await videoRef.getDownloadURL()
newCardData.video = videoUrl
}
newCardData.urlsId = cardMediaRef
const promiseArr = this.newCard.cardImages.map(async (image) => {
const imageRef = cardImagesRef.child(uuidV4())
await imageRef.put(image)
const imageUrl = await imageRef.getDownloadURL()
newCardData.images.push(imageUrl)
})
await Promise.all(promiseArr)
},
async postNewCardToDb() {
this.newCard.loading = true
const newCardData = {
urlsId: '',
title: this.newCard.cardTitle,
description: this.newCard.cardSnippet,
video: '',
images: [],
}
await this.getMediaUrlsFromStorage(newCardData)
await this.$fire.database.ref('albums/cards').push(newCardData)
console.log(newCardData)
this.newCard.loading = false
this.newCard.cardTitle = null
this.newCard.cardSnippet = null
this.newCard.cardImages = []
this.newCard.cardImageUrls = []
this.newCard.cardVideo = null
},
},
}
</script>
First-child is a card component and I need to pass props of each card to Second-child without calling it. I cant call Second-child in First-child because of iteration.
I hope I expleined it well

using vuetify overlay in datatable

So I have a datatable of images that I want to expand in an overlay tag on click.
To do that, I created an array for each column of the table and I mapped it to its corresponding image.
Here's the code :
<template>
<v-app>
<app-navbar />
<v-main>
<div class="text-center">
<h3>
test {{ $route.params.name }}, {{ $route.query.status }},{{
$route.query.tag
}}
</h3>
</div>
<v-data-table
:headers="headers"
:items="imagesref"
:items-per-page="5"
class="elevation-1"
>
<template v-slot:[`item.index`]="{ index }">
{{index+1}}
</template>
<template v-slot:[`item.ref`]="{ index }">
<v-img :src="imagesref[index]" max-width="750" max-height="750" #click="expref[index] = !expref[index]"/>
<v-overlay :value="expref[index]"><v-img :src="imagesref[index]" max-width="1300" max-height="900" #click="expref[index] = !expref[index]"/> </v-overlay>
</template>
<template v-slot:[`item.test`]="{ index }">
<v-img :src="imagestest[index]" max-width="750" max-height="750" #click="exptest[index] = !exptest[index]"/>
<v-overlay :value="exptest[index]"><v-img :src="imagestest[index]" max-width="1300" max-height="900" #click="exptest[index] = !exptest[index]"/> </v-overlay>
</template>
<template v-slot:[`item.res`]="{ index }">
<v-img :src="imagesresult[index]" max-width="750" max-height="750" #click="expres[index] = !expres[index]"/>
<v-overlay :value="expres[index]"><v-img :src="imagesresult[index]" max-width="1300" max-height="900" #click="expres[index] = !expres[index]"/> </v-overlay>
</template>
<template #[`item.Scrubber`]="{ index }">
<nuxt-link :to="{ path: 'scrubber', query: { imageref: imagesref[index],imagetest:imagestest[index],imageres:imagesresult[index] }}">Show Scrubber</nuxt-link>
</template>
</v-data-table>
</v-main>
</v-app>
</template>
<script>
import appNavbar from "../../../components/appNavbar.vue"
import axios from "axios"
export default {
components: { appNavbar },
name: "App",
data() {
return {
expref:[],
exptest:[],
expres:[],
items: [],
imagesref: [],
imagestest: [],
imagesresult: [],
headers: [
{ text: 'index',value: 'index',sortable:false},
{ text: 'Imagesref', value: 'ref',sortable:false },
{ text: 'Imagestest', value: 'test',sortable:false },
{ text: 'Imagesresult', value: 'res',sortable:false },
{ text: 'Scrubber', value: 'Scrubber',sortable:false },
]
}
},
async created() {
try {
const res = await axios.get(`http://localhost:3004/tests`, {
params: { name: this.$route.params.name },
})
this.items = res.data
this.imagesref = res.data[0].refimages
this.imagestest = res.data[0].testimages
this.imagesresult = res.data[0].resultimages
for (let i of this.imagesref){
this.expref.push(false);
this.exptest.push(false);
this.expres.push(false);
}
} catch (error) {
console.log(error)
}
}
}
</script>
<style scoped>
</style>
When I tested it, after I click on the image the corresponding variable in the array changes its value to true but the overlay is not getting displayed but somehow when I change the value manually on devtools it works.Does someone have any idea what's going on and how can i make it work ?

Error: vuex do not mutate vuex store state outside mutation handlers With uploading image

i'm kinda stuck. i get this error every time i try to update my store. what i'm doing is, i fetch my product and store it. here on upload image section (component) at created fill the image with state to show the already uploaded images, then on each upload change my state via dispatch and mutation. i know it has something to do with created() when i fill my data with state, but don't know how to solve that.
here is my template:
<v-card flat class="pb-4" max-width="450px">
<v-img class="rounded-lg theme__main__color" :src="url"></v-img>
<v-list container inline class="transparent text-center pa-0">
<v-list-item class="px-0">
<v-list-item class="px-1">
<v-btn
width="100%"
class="theme__btn__w text_main_color"
:loading="isSelecting"
#click="onChooseClick"
>{{lang.chooseimage}}</v-btn>
</v-list-item>
<v-list-item class="px-1">
<v-btn
width="100%"
class="theme__btn__s text_main_color"
#click.prevent="uploadImage"
>{{lang.upload}}</v-btn>
<input
ref="uploader"
class="d-none"
type="file"
accept="image/*"
#change="onFileChanged"
>
</v-list-item>
</v-list-item>
</v-list>
</v-card>
and this is my script:
export default {
data(){
return{
isSelecting: false,
selectedFile: null,
url: null,
placeholder: '/images/placeholder/place-800.png',
section: 'img',
pimages: [],
pId: null
}
},
methods:{
onChooseClick(){
this.isSelecting = true
window.addEventListener('focus', () => {
this.isSelecting = false
}, { once: true })
this.$refs.uploader.click()
},
onFileChanged(e) {
this.selectedFile = e.target.files[0]
this.url = URL.createObjectURL(this.selectedFile)
// do something
},
async uploadImage(){
if(this.notEmpty(this.selectedFile) && this.notEmpty(this.pId)){
const data = new FormData()
data.append('image', this.selectedFile)
data.append('pId', this.pId)
// let response = await this.axiosPost('product/createproimg', data)
this.pimages.push({"url": this.url})
this.url = this.placeholder
this.setEditProductImg(this.pimages)
}
}
},
created(){
this.url = this.placeholder
this.pId = this.editProduct.pId
this.pimages = this.editProduct.images
this.$nuxt.$on('insert',(section)=>{
if(section === 'desc' && !this.pId){
this.pId = this.editProduct.pId
}
})
}
}
and of course my store:
state:
editProduct: {
pId: null,
images: []
}
getter:
editProduct(state){
return state.editProduct
}
mutation:
SET_EDITPRODUCT_IMG(state, img){
state.editProduct.images = img
},
action:
setEditProductImg({commit}, img){
commit('SET_EDITPRODUCT_IMG', img)
},
UPDATE
thanks to #skirtle the above problem has been solved! but got a new same error on something else. as advise by #skirtle used const to mutate my state but get error. to be more clear, my state is an empty array, it will be filled the first time, but i get the error if i even try to change my select!!! let alone send another mutation!! here is the codes:
<template>
<div class="pt-6">
<v-row class="ma-0">
<v-col cols="12" md="12" class="pa-0">
<v-row class="ma-0">
<!-- form 1 -->
<template v-for="(select, index) in selects">
<component
:is="select"
:key="select.name"
v-model="catId"
#changed="addComponent(index)"
:catid="catId"
:selectindex="index"
:pcat="productCat[index]"
:subcat="subCat"
></component>
</template>
<!-- btn -->
<v-col cols="12" sm="6" md="3" class="px-1">
<v-btn width="100%" class="theme__little__color2 text_main_color px-2" #click.prevent="addCatBtn()">{{lang.addcat}}</v-btn>
</v-col>
{{selectedCatArr}}
<!-- btn -->
<addproductbtn :section="section" />
</v-row>
</v-col>
</v-row>
</div>
</template>
<script>
import addproductbtn from '~/components/global/cms/addproductbtn'
import selectcategory from '~/components/global/cms/selectcategory'
export default {
components:{
'addproductbtn': addproductbtn
},
data(){
return{
section: 'cat',
selects: [selectcategory],
catId: 0,
subCat: true,
selectedCatArr: []
}
},
methods:{
addComponent(index){
this.selects.length = index + 1
setTimeout(() => {
this.selects.push(selectcategory)
}, 1);
},
addCatBtn(){
this.goToRedirect('/cms/category/insert', this.$route.path, this.ProductId)
},
async insertCategory(){
const data = {
pId: this.editProduct.pId,
catId: this.catId
}
// let response = await this.axiosPost('product/catupdate', data)
const productCategory = this.selectedCatArr
this.setEditProductCat(productCategory)
}
},
computed:{
productCat(){
return this.editProduct.categories
},
ProductId(){
return this.editProduct.pId
}
},
created(){
this.$nuxt.$on('insert', ()=>{
this.insertCategory()
})
this.$nuxt.$on('nextcat', (subCat)=>{
this.subCat = subCat
})
this.$nuxt.$on('nextpanel', ()=>{
this.insertCategory()
})
this.$nuxt.$on('selectedcat', (selected, index)=>{
delete selected.subCategory
this.selectedCatArr.length = index
this.selectedCatArr.push(selected)
})
}
}
</script>
and my select component:
<template>
<v-col cols="12" sm="6" md="3" class="px-1 text_details_color3" v-if="showCat">
<v-select
return-object
:items="items"
:label="lang.category"
v-model="selected"
#change="emitEvent"
item-text="title"
item-value="id"
outlined></v-select>
{{selected}}
</v-col>
</template>
<script>
export default {
props:['selectindex','catid','pcat','subcat'],
data(){
return{
selected:{},
items:[],
showCat: true
}
},
async fetch(){
// this.items = await this.axiosGet(`categories/${this.catid}/1`)
this.items = [
{id: this.catid + 1, title: this.catid+'title1', subCategory: true},
{id: this.catid + 2, title: this.catid+'title2', subCategory: true},
{id: this.catid + 3, title: this.catid+'title3', subCategory: false},
{id: this.catid + 4, title: this.catid+'title4', subCategory: true}
]
},
methods:{
emitEvent(){
this.$emit('input', this.selected.id)
this.$emit('changed')
$nuxt.$emit('nextcat', this.selected.subCategory)
$nuxt.$emit('selectedcat', this.selected, this.selectindex)
}
},
computed:{
//
},
created(){
},
mounted(){
this.selected = this.pcat
this.showCat = this.subcat
}
}
</script>
I believe the problem is this line:
this.pimages.push({"url": this.url})
The array this.pimages is the same array that's inside the store state and by calling push you're modifying it outside the store.
There are a couple of ways you could fix this.
One way would be to perform the push inside a mutation, e.g. by having an ADD_EDITPRODUCT_IMG mutation:
mutation:
ADD_EDITPRODUCT_IMG(state, img){
state.editProduct.push(img)
},
You'd then call that in much the same way as with your current mutation, except that you'd just pass it the new image to add rather than passing the full array.
An alternative approach that is a bit closer to what you currently have would be to take a copy of the array rather than modifying the original:
const newImages = [...this.pimages, {"url": this.url}]
this.setEditProductImg(newImages)
Using this approach you wouldn't need any changes to your existing store.
Update:
I also suggest making pimages a computed property. There doesn't seem to be any good reason to 'copy' the data out of the store state in a created hook.

Prevent vuetify checkbox from checking itself?

I want the v-checkbox to remain unchanged after I write in the text field.
What is happening:
The checkbox checks itself whenever I click outside the box or type in the text field below.
What is expected:
The checkbox to remain unchecked until the use checks it.
Code:
export default {
props: ['answer'],
data() {
return {
newAnswer: 'Your answer here...',
multiChoiceAnswers: {
answers: ['test1', 'test2'],
selected: [],
}
}
},
created() {
if (this.answer.answers.length > 1) {
this.multiChoiceAnswers.answers = this.answer.answers
this.multiChoiceAnswers.selected = this.answer.selected
} else {
console.log('Answer Template Generated')
}
},
methods: {
selectedAnswer(clickEvent, index) {
console.log(clickEvent, index)
//Checking wether or not the answer is a value or null
//in order to push or remove it from the selected answers
if (clickEvent === this.multiChoiceAnswers.answers[index]) {
this.multiChoiceAnswers.selected.push(clickEvent)
} else {
const selectedPosition = this.multiChoiceAnswers.selected.indexOf(this.multiChoiceAnswers.answers[index])
this.multiChoiceAnswers.selected.splice(selectedPosition, selectedPosition + 1)
}
this.$emit('newAnswer', this.multiChoiceAnswers)
},
changedAnswer(changedAnswer, index) {
console.log(changedAnswer)
//Getting previous selected answer position to replace later
const selectedPosition = this.multiChoiceAnswers.selected.indexOf(this.multiChoiceAnswers.answers[index])
//Changing the current value of answer[index] to input value in answers
this.multiChoiceAnswers.answers.splice(index, index + 1, changedAnswer)
//Changing the current value of answer[index] to input value in selected
this.multiChoiceAnswers.selected.splice(selectedPosition, selectedPosition + 1, changedAnswer)
this.$emit('newAnswer', this.multiChoiceAnswers)
},
Here is the template code:
<v-container>
<div :key="(answer, index)" v-for="(answer, index) in multiChoiceAnswers.answers">
<v-layout align-center>
<v-checkbox hide-details class="shrink mr-2" #click.prevent #change="selectedAnswer($event, index)" :value="answer"></v-checkbox>
<v-text-field class="checkbox-input" #input="changedAnswer($event, index)" :placeholder="answer"></v-text-field>
<v-btn #click="removeAnswer(index)">Remove</v-btn>
</v-layout>
</div>
</v-container>
Seems like I must have been to tired or drunk when I posted this ;)
Here is the completely new revised code I did to fix this solution:
<template>
<div>
<v-container>
<div :key="(answer, index)" v-for="(answer, index) in multiChoiceAnswers.answers">
<v-layout align-center>
<v-checkbox hide-details class="shrink mr-2" v-model="answer.selected"></v-checkbox>
<v-text-field class="checkbox-input" v-model="answer.answerText" :placeholder="answer.answerText"></v-text-field>
<v-btn #click="removeAnswer(index)">Remove</v-btn>
</v-layout>
</div>
</v-container>
<v-btn #click="newAnswerOption">Add Answer</v-btn>
</div>
<script>
export default {
props: ['answer'],
data() {
return {
newAnswer: { answerText: 'Your answer here...', selected: false },
multiChoiceAnswers: {
answers: [
{ answerText: 'test1', selected: false },
{ answerText: 'test2', selected: false }
],
},
}
},
created() {
if (this.answer.answers.length > 1) {
this.multiChoiceAnswers.answers = this.answer.answers
} else {
console.log('Answer Template Generated')
}
},
methods: {
newAnswerOption() {
this.multiChoiceAnswers.answers.push(this.newAnswer)
this.$emit('newAnswer', this.multiChoiceAnswers)
},
removeAnswer(index) {
//Removing the answer
this.multiChoiceAnswers.answers.splice(index, 1)
this.$emit('newAnswer', this.multiChoiceAnswers)
}
}
}
</script>
What has changed?
I deleted all the previous code that was broken and necessarily complex.
I created a new data array with the objects answers. Each object now has the answerText (string) and the selected (boolean).
The checkbox is now connected to change the answers.selected with v-model
The input is now connected to change the answers.answerText with v-model

vuetify autocomplete allow unknown items between chips

I am trying to modify the sample code at https://vuetifyjs.com/en/components/autocompletes#example-scoped-slots to allow arbitrary content not matching any autocomplete items in between chips (so user can tag other users in a message similar to slack and facebook)
So for example, the user could type "Sandra" and then select "sandra adams", then type "foo" and then type another space and start typing "John" and the autcomplete would pop up again and allow the user to select "John Smith".
I've been through all the properties in the docs and there doesn't seem to be support for this built in.
I tried using custom filtering to ignore the irrelevant parts of the message when displaying autocomplete options, but the autocomplete seems to remove non-chip content when it loses focus and I can't see a property that allows me to prevent this behavior.
not sure if the autcomplete is the thing to be using or if I would be better off hacking combo box to meet this requirement, because this sample seems closer to what I'm tryng to do https://vuetifyjs.com/en/components/combobox#example-no-data, but then I believe I lose the ajax capabilities that come with automcomplete.
You can achieve this by combining the async search of the autocomplete with the combobox.
For example:
new Vue({
el: '#app',
data: () => ({
activator: null,
attach: null,
colors: ['green', 'purple', 'indigo', 'cyan', 'teal', 'orange'],
editing: null,
descriptionLimit: 60,
index: -1,
nonce: 1,
menu: false,
count: 0,
model: [],
x: 0,
search: null,
entries: [],
y: 0
}),
computed: {
fields () {
if (!this.model) return []
return Object.keys(this.model).map(key => {
return {
key,
value: this.model[key] || 'n/a'
}
})
},
items () {
return this.entries.map(entry => {
const Description = entry.Description.length > this.descriptionLimit
? entry.Description.slice(0, this.descriptionLimit) + '...'
: entry.Description
return Object.assign({}, entry, { Description })
})
}
},
watch: {
search (val, prev) {
// Lazily load input items
axios.get('https://api.publicapis.org/entries')
.then(res => {
console.log(res.data)
const { count, entries } = res.data
this.count = count
this.entries = entries
})
.catch(err => {
console.log(err)
})
.finally(() => (this.isLoading = false))
/*if (val.length === prev.length) return
this.model = val.map(v => {
if (typeof v === 'string') {
v = {
text: v,
color: this.colors[this.nonce - 1]
}
this.items.push(v)
this.nonce++
}
return v
})*/
},
model (val, prev) {
if (val.length === prev.length) return
this.model = val.map(v => {
if (typeof v === 'string') {
v = {
Description: v
}
this.items.push(v)
this.nonce++
}
return v
})
}
},
methods: {
edit (index, item) {
if (!this.editing) {
this.editing = item
this.index = index
} else {
this.editing = null
this.index = -1
}
},
filter (item, queryText, itemText) {
const hasValue = val => val != null ? val : ''
const text = hasValue(itemText)
const query = hasValue(queryText)
return text.toString()
.toLowerCase()
.indexOf(query.toString().toLowerCase()) > -1
}
}
})
<link href='https://fonts.googleapis.com/css?family=Roboto:100,300,400,500,700,900|Material+Icons' rel="stylesheet">
<link href="https://cdn.jsdelivr.net/npm/vuetify/dist/vuetify.min.css" rel="stylesheet">
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<script src="https://cdn.jsdelivr.net/npm/vuetify/dist/vuetify.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/axios/0.18.0/axios.min.js" integrity="sha256-mpnrJ5DpEZZkwkE1ZgkEQQJW/46CSEh/STrZKOB/qoM=" crossorigin="anonymous"></script>
<div id="app">
<v-app>
<v-content>
<v-container>
<v-combobox
v-model="model"
:filter="filter"
:hide-no-data="!search"
:items="items"
:search-input.sync="search"
hide-selected
label="Search for an option"
:allow-overflow="false"
multiple
small-chips
solo
hide-selected
return-object
item-text="Description"
item-value="API"
:menu-props="{ closeOnClick: false, closeOnContentClick: false, openOnClick: false, maxHeight: 200 }"
dark
>
<template slot="no-data">
<v-list-tile>
<span class="subheading">Create</span>
<v-chip
label
small
>
{{ search }}
</v-chip>
</v-list-tile>
</template>
<template
v-if="item === Object(item)"
slot="selection"
slot-scope="{ item, parent, selected }"
>
<v-chip
:selected="selected"
label
small
>
<span class="pr-2">
{{ item.Description }}
</span>
<v-icon
small
#click="parent.selectItem(item)"
>close</v-icon>
</v-chip>
</template>
<template
slot="item"
slot-scope="{ index, item, parent }"
>
<v-list-tile-content>
<v-text-field
v-if="editing === item.Description"
v-model="editing"
autofocus
flat
hide-details
solo
#keyup.enter="edit(index, item)"
></v-text-field>
<v-chip
v-else
dark
label
small
>
{{ item.Description }}
</v-chip>
</v-list-tile-content>
</template>
</v-combobox>
</v-container>
</v-content>
</v-app>
</div>
so I ended up building a renderless component that is compatible with vuetify as it goes through the default slot and finds any of the types of tags (textarea, input with type of text, or contenteditable) that tribute supports, and allows you to put arbitrary vue that will be used to build the tribute menu items via a scoped slot.
in future might try to wrap it as a small NPM package to anyone who wants a declarative way to leverage tribute.js for vue in a more flexible way than vue-tribute allows, but for now here's my proof of concept
InputWithMentions.vue
<script>
import Tribute from "tributejs"
// eslint-disable-next-line
import * as css from "tributejs/dist/tribute.css"
import Vue from "vue"
export default {
mounted() {
let menuItemSlot = this.$scopedSlots.default
let tribute = new Tribute({
menuItemTemplate: item =>
{
let menuItemComponent =
new Vue({
render: function (createElement) {
return createElement('div', menuItemSlot({ menuItem: item }))
}
})
menuItemComponent.$mount()
return menuItemComponent.$el.outerHTML
},
values: [
{key: 'Phil Heartman', value: 'pheartman'},
{key: 'Gordon Ramsey', value: 'gramsey'}
]})
tribute.attach(this.$slots.default[0].elm.querySelectorAll('textarea, input[type=text], [contenteditable]'))
},
render(createElement) {
return createElement('div', this.$slots.default)
}
}
</script>
User.vue
<InputWithMentions>
<v-textarea
box
label="Label"
auto-grow
value="The Woodman set to work at once, and so sharp was his axe that the tree was soon chopped nearly through.">
</v-textarea>
<template slot-scope="{ menuItem }">
<v-avatar size="20" color="grey lighten-4">
<img src="https://vuetifyjs.com/apple-touch-icon-180x180.png" alt="avatar">
</v-avatar>
{{ menuItem.string }}
</template>
</InputWithMentions>