Prevent vuetify checkbox from checking itself? - vue.js

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

Related

Copy text upon clicking on icon in v-text-field

I'm trying to figure out how to allow users to copy their login details when they click the copy icon. How to get the value of the relevant v-text-field?
I thought I should use #click:append and link it to a method. However, I struggle how to get a value.
<template>
<v-card class="col-12 col-md-8 col-lg-6 p-6 px-16" elevation="4">
<div class="title h2 mb-10 text-uppercase text-center">
Success
<v-icon color="green" x-large>
mdi-check-circle
</v-icon>
</div>
<v-text-field
:value="newAccount.login"
label="Login"
outlined
readonly
append-icon="mdi-content-copy"
#click:append="copy('login')"
></v-text-field>
<v-text-field
:value="newAccount.password"
label="Password"
outlined
readonly
append-icon="mdi-content-copy"
></v-text-field>
</v-card>
</template>
<script>
export default {
props: ["newAccount"],
data() {
return {
copied: false,
};
},
methods: {
copy(target) {
if (target === "login") {
console.log("login is clicked");
}
},
},
computed: {},
};
</script>
The value of the v-text-field is available from its value property. Apply a template ref on the v-text-field to get a reference to the component programmatically from vm.$refs, then use .value off of that:
<template>
<v-text-field
ref="login"
#click:append="copy('login')"
></v-text-field>
</template>
<script>
export default {
methods: {
copy(field) {
console.log('value', this.$refs[field].value)
}
}
}
</script>
Alternatively, you could access the nested template ref of v-text-field's <input>, which has a ref named "input", so copy() would access it from this.$refs[field].$refs.input. Then, you could select() the text value, and execute a copy command:
export default {
methods: {
copy(field) {
const input = this.$refs[field].$refs.input
input.select()
document.execCommand('copy')
input.setSelectionRange(0,0) // unselect
}
}
}
demo

Can't add Object to an Array

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

Updating Vuex following Drag and Drop using vuedraggable

I have created a Vue application that uses Vuetify, Vuex and vuedraggable. The component functions as expected with drag and drop of v-card containing the associated information but I have not been able to find a way to identify the destination so that I can update the Vuex Store
<template>
<v-container>
<v-row justify='center'>
<v-col v-for="stage in stages" :key="stage.value">
<span class="card-text-bold">{{ stage.heading }}</span>
<draggable :list="buckets[stage.name]" group="openTasks" :move="handleStatus">
<v-card color="commentCard" class="list-group mt-3" v-for="task in buckets[stage.name]" :key="task._id">
<v-card-text>
<span class="card-text-bold">{{ task.title}}</span>
</v-card-text>
<v-card-text>
<span class="card-text"> {{ task.description}} </span>
</v-card-text>
<v-card-text>
{{ task.assignee}}<v-icon right #click="goToTask(task._id)">edit</v-icon>
</v-card-text>
</v-card>
</draggable>
</v-col>
</v-row>
</v-container>
</template>
<script>
import { mapGetters } from 'vuex';
import draggable from 'vuedraggable';
import moment from 'moment';
export default {
name: 'Tasks',
components: {
draggable
},
data() {
return {
stages: [
{ heading: 'Created', name: 'created' },
{ heading: 'Assigned', name: 'assigned' },
{ heading: 'In Progress', name: 'in progress' },
{ heading: 'On Hold', name: 'on hold' },
{ heading: 'Complete', name: 'complete' },
{ heading: 'Closed', name: 'closed' }
]
};
},
created() {
this.handleGetTasks();
},
computed: {
...mapGetters(['user', 'loading', 'tasks', 'buckets'])
},
methods: {
getTimeFromNow(time) {
return moment(new Date(time)).fromNow();
},
goToTask(taskId) {
this.$router.push(`/task/${taskId}`);
},
handleGetTasks() {
const userRole = localStorage.getItem('role');
const fullname = localStorage.getItem('fullname');
switch (userRole) {
case 'Manager': {
this.$store.dispatch('getAllTasks');
break;
}
case 'Requester': {
this.$store.dispatch('getRequestTasks', {
fullname: fullname
});
break;
}
case 'Assignee': {
this.$store.dispatch('getAssignTasks', {
fullname: fullname
});
break;
}
}
},
stateTasks(target) {
console.log('state', target);
if (this.buckets[target] > 0) return this.buckets[target];
else return [];
},
handleStatus(evt) {
const movedId = evt.draggedContext.element._id;
var targetStage;
console.log('source', movedId, 'target', Object.keys(evt.relatedContext.list));
}
}
};
</script>
The source list for data used here is the 'buckets' object which contains an array of tasks for each of the task stages. Drag and Drop moves the task cards from the source to the target but I have been able to find a way to identify the target list.
Please provide advice about how I can determine which of the lists in the 'buckets' object should be updated following the move.
Thanks
Des
If you take a look at the documentation, you'll see that vuedraggable has a #end event which will trigger after moving an element. When logging that event you'll notice quite a lot of useful information - such as oldIndex, newIndex and more..
<draggable :list="list" #end="onEnd">
Source: https://github.com/SortableJS/Vue.Draggable#events
I was able to solve the problem by parsing the HTML element of the move event.
The header for each of the stage columns can then be used as the identifier of the target list.
Here is the code
<template>
<v-container>
<v-row justify='center'>
<v-col v-for="(stage, index) in stages" :key='index'>
<draggable :list="buckets[stage]" group="openTasks" :move='handleMove'>
<span slot="header" :tag="stage" class="card-text-bold">{{ stage }}</span>
<v-card color="commentCard" class="list-group mt-3" v-for="task in buckets[stage]" :key="task._id">
<v-card-text>
<span class="card-text-bold">{{ task.title}}</span>
</v-card-text>
<v-card-text>
<span class="card-text"> {{ task.description}} </span>
</v-card-text>
<v-card-text>
{{ task.assignee}}<v-icon right #click="goToTask(task._id)">edit</v-icon>
</v-card-text>
</v-card>
</draggable>
</v-col>
</v-row>
</v-container>
</template>
<script>
import { mapGetters } from 'vuex';
import draggable from 'vuedraggable';
import moment from 'moment';
export default {
name: 'Tasks',
components: {
draggable
},
data() {
return {
stages: ['created', 'assigned', 'in progress', 'on hold', 'complete', 'archive']
};
},
created() {
this.handleGetTasks();
},
computed: {
...mapGetters(['user', 'loading', 'tasks', 'buckets'])
},
methods: {
getTimeFromNow(time) {
return moment(new Date(time)).fromNow();
},
goToTask(taskId) {
this.$router.push(`/task/${taskId}`);
},
handleGetTasks() {
const userRole = localStorage.getItem('role');
const fullname = localStorage.getItem('fullname');
switch (userRole) {
case 'Manager': {
this.$store.dispatch('getAllTasks');
break;
}
case 'Requester': {
this.$store.dispatch('getRequestTasks', {
fullname: fullname
});
break;
}
case 'Assignee': {
this.$store.dispatch('getAssignTasks', {
fullname: fullname
});
break;
}
}
},
handleMove(evt) {
const movedId = evt.draggedContext.element._id;
var stageTag = evt.relatedContext.component.rootContainer.firstElementChild.innerText;
if (this.stages.includes(stageTag)) {
this.$store.dispatch('changeStatus', {
taskId: movedId,
status: stageTag
});
}
}
}
};
Decoding the component HTML is done with this
evt.relatedContext.component.rootContainer.firstElementChild.innerText;

How to filter a product list using v-btn in vuetify?

I am currently trying to filter a list of objects by using buttons.
I know that both v-btn and v-card loops are not currently interdependent, but I am unable to figure out how to link both sections to properly show only the correct category when pressing its respective button.
Thanks for the help!
<v-row class="my-4">
<v-btn
v-for="category in categories"
text
class="underline"
route :to="category.route"
#click="category.function"
>{{ category.text }}</v-btn>
</v-row>
<v-row
justify="start">
<v-card
v-for="product in products"
:key="product.name"
class="ma-3 text-center">
<v-img
:src="product.src"
class="mc mt-2"
>
</v-img>
<v-card-title
class="bold justify-center">
<h4>{{ product.name }}</h4>
</v-card-title>
</v-card>
</v-row>
categories: [
{ text: 'All', function: "all()" },
{ text: 'Fruits & Veggies', function: "fruitsVeggies()" },
{ text: 'Baking', function: "baking" },
{ text: 'Gadgets', function: "gadgets" },
{ text: 'Cutting Boards', function: "cuttingBoards"}],
products: [{...}, {...}, etc]
computed: {
all() {
return this.products.filter(product => {
return product
})
},
fruitsVeggies() {
return this.products.filter(product => {
return product.category === 'Fruits & Veggies'
})
},
baking() {
return this.products.filter(product => {
return product.category === 'Baking'
})
},
cuttingBoards() {
return this.products.filter(product => {
return product.category === 'Cutting Boards'
})
}
Vue computed properties return a dynamic value
When you call baking as a function, it should throw an error in the console; it's not a function, but a property
Instead, you could define a computed property called filteredProducts that changes filter based on something stored in the data, and loop over that instead
data() {
productFilter: "all"
}
computed: {
// your computed values
filteredProducts() {
if (this.productFilter === "all") {
return this.all;
}
// etc. For all filters
}
}
Then in your template:
<v-row
justify="start"
>
<v-card
v-for="product in filteredProducts"

VueJS - How to pass function to global component

I have a confirm dialog, which should be shown when users perform delete action. I need to make it works globally (Many pages can use this component by passing confirm message and delete function to it). However, I haven't found a way to pass a function to this component.
Thanks in advance!
ConfirmDialog component:
<template>
<v-dialog
v-model="show"
persistent
max-width="350"
>
<v-card>
<v-card-text class="text-xs-center headline lighten-2" primary-title>
{{ message }}
</v-card-text>
<v-card-actions class="justify-center">
<v-btn color="back" dark #click="close">キャンセル</v-btn>
<v-btn color="primary" dark>削除</v-btn>
</v-card-actions>
</v-card>
</v-dialog>
</template>
<script>
export default {
data () {
return {
show: false,
message: ''
}
},
created: function () {
this.$store.watch(state => state.confirmDialog.show, () => {
const msg = this.$store.state.confirmDialog.message
if (msg !== '') {
this.show = true
this.message = this.$store.state.confirmDialog.message
} else {
this.show = false
this.message = ''
}
})
},
methods: {
close () {
this.$store.commit('closeDialog')
}
}
}
</script>
ConfirmDialog store:
export default {
state: {
show: false,
message: '',
submitFunction: {}
},
getters: {
},
mutations: {
showDialog (state, { message, submitFunction }) {
state.show = true
state.message = message
state.submitFunction = submitFunction
},
closeDialog (state) {
state.show = false
state.message = ''
}
}
}
you can get and set states easily.
try getting the value of show with ...mapState
ConfirmDialog.vue :
<template>
<v-dialog
v-if="show"
persistent
max-width="350"
>
<v-card>
<v-card-text class="text-xs-center headline lighten-2" primary-title>
{{ message }}
</v-card-text>
<v-card-actions class="justify-center">
<v-btn color="back" dark #click="close">キャンセル</v-btn>
<v-btn color="primary" dark>削除</v-btn>
</v-card-actions>
</v-card>
</v-dialog>
</template>
<script>
import { mapState } from 'vuex';
export default {
data () {
return {
show: false,
message: ''
}
},
methods: {
close () {
this.$store.commit('closeDialog')
}
},
computed: {
...mapState({
show: 'show'
})
}
}
</script>
The store is, as the name says, a store. You have a centralized tree where you save data, not functionalities. Another reason is that functions are not serializable.
You could create this component in a global way by injecting the function as prop or by using emit and handling the functionality in the parent.