Initializing checkboxes in vue-bootstrap b-table rows - vue.js

I am working on a page using vue-bootstrap that has a b-form-checkbox-group embedded in each row. Those are appearing fine. The problem comes when I try to assign values to the checked prop. When i assign a static number the boxes will be checked like they should be when the page loads but when I try to set it up to receive responses from jquery get calls the boxes are only sometimes filled with no pattern as far as I can see.
<nit-card :title="host">
<b-table
:items="interfaceNames"
:fields="switch_interface_config_col_names">
<template slot="tagged" slot-scope="row">
<b-form-checkbox-group :options="vlans"
:checked="interfaceNames[row.index].taggedVlans"
stacked>
</b-form-checkbox-group>
</template>
<template slot="untagged" slot-scope="row">
<b-form-radio-group :options="vlans"
:checked="interfaceNames[row.index].untaggedVlans"
stacked>
</b-form-radio-group>
</template>
</b-table>
</nit-card>
Above is the html for the table. nit-card is a vue.component that looks like this.
Vue.component('nit-card', {
props: ['title'],
template: `
<div class="card">
<div class="card-header">
<h2 class="card-title">{{ title }}</h2>
</div>
<div class="card-body">
<slot></slot>
</div>
</div>
`
})
And some of the js code with an exmaple of ine of the methods that are called to get the data for the checked prop.
let generic_name = new Vue({
el: '#generic_name',
data: function(){
return{
interfaceNames: [],
vlans: [],
switch_interface_config_col_names: [
{key: "interfaceName", sortable: true},
{key: "description", sortable: false},
{key: "tagged", sortable: false},
{key: "untagged", sortable: false},
],
submit_params: [
["Switch", "text", "Hostname or IP", true],
["Interface", "text", "Interface Name", true],
],
host: "",
}
},
methods: {
getTagged: function(){
let that = this
for(let i = 0; i < that.interfaceNames.length; i++){
let url_encoding_re = /\//g;
let interfaceName = that.interfaceNames[i].interfaceName.replace(url_encoding_re, '%5c')
$.get(api_url + "/switches/" + that.host + "/interfaces/" + interfaceName + "/vlans/tagged", function(response) {
console.log(response)
that.interfaceNames[i].taggedVlans = response.vlans
})
}
},
}
The way that I imagine it flowing is that the interfaceNames array stores the data and then it is accessed by the html using the row.index value. What is actually happening is that maybe half of the checkbox forms have the checked values in their checked prop even though when I look at the root vue the values are present in interfaceNames[x].taggedVlans.
What do i need to do to have the checked prop filled consistently for these check-box-groups?
EDIT: By toggling the table with a v-if manually (Ex a button after the table renders) all of the checkboxs will appear correctly. Once I have a reliable way to toggle automatically the table I will post it here.

The issue came from how i was adding the objects to the interfaceNames array. Vue.js cant detect changes made to arrays when you directly set the value through the index. To make the array reactive use Vue.set(this.arrayName, indexValue, new value) (as seen here https://v2.vuejs.org/v2/guide/list.html#Caveats)
For this specific example above Vue.set(that.interfaceNames, i, {key: value, key: value, ...}) worked. Since it was done in a loop for all the values of the array the entire array was made reactive.

Related

Connect v-select with vuex: problem [object object]

I am trying to create a dropdown (v-select/q-select (using quasar)), which allows me to select from an array in my vuex-storage and then eventually save the selected item (content of it) in a variable. Currently I have no problem to access the vuex-storage, but face the problem, that the v-select expects a string and not an object.
My code looks like the following.
// vuex storage:
const state = {
savedsystems:
[
id: "1",
system: {...}
],
[
id: "2",
system: {...}
]
// example of the vuex storage out of my viewdevtools
systemsconstant: Object
savedsystems:Array[2]
0:Object
id:"first"
system:Object
7a73d702-fc28-4d15-a54c-2bb950f7a51c:Object
name:"3"
status:"defined"
88519419-8a81-48f1-a5e6-5da77291b848:Object
name:"5"
status:"not defined"
1:Object
id:"second"
system:Object
7a73d702-fc28-4d15-a54c-2bb950f7a51c:Object
name:"3"
status:"not defined"
88519419-8a81-48f1-a5e6-5da77291b848:Object
name:"9"
status:"defined"
}
// dropdown:
<q-select
outlined
dense
emit-value
:value="currentsystem"
:options="savedsystems"
label="selectsystem" />
// computed to get systems from vuex:
computed: {
savedsystems() {
return this.$store.getters['systemsconstant/getsavedsystems']
}
},
I used the following example https://codepen.io/sagalbot/pen/aJQJyp as inspiration and tried a couple of different setups stringifying resulting in nothing really.
If one would try to apply my case to a similar problem (v-select displays object Object), the mentioned formatlabel would be an object instead of a string.
Question:
How can I modify the (with a getter) imported array of objects "savedsystems", so it can be used both as label to select it and furthermore then to connect it properly to the values, so I can save the selected as a variable.
Or can I change something in my v-select, e.g. varying what comes behind :options/options?
I'd appreciate any help!
You should use the property option-label
<div id="q-app">
<div class="q-pa-md" style="max-width: 300px">
<div class="q-gutter-md">
<q-badge color="secondary" multi-line>
Model: "{{ model }}"
</q-badge>
<q-select filled v-model="model" :options="options" label="Standard" option-label="description"></q-select>
{{ model }}
</div>
</div>
</div>
JS:
new Vue({
el: '#q-app',
data () {
return {
model: null,
options: [
{
label: 'Google',
value: 'Google',
description: 'Search engine',
category: '1'
},
{
label: 'Facebook',
value: 'Facebook',
description: 'Social media',
category: '1'
},
{
label: 'Twitter',
value: 'Twitter',
description: 'Quick updates',
category: '2'
},
]
}
}
})
https://codepen.io/reijnemans/pen/bGpqJYx?editors=1010

Vuejs, Dynamic b-table with editable fields and two-way databinding

I'm trying to generate a dynamic b-table with editable fields and with two-way databinding.
I would like to not have any hardcoded values. Now, I have:
<b-table striped hover :items="filtered">
<template v-slot:cell(issueDescription)="row">
<b-form-input v-model="row.item.issueDescription" />
</template>
<template v-slot:cell(endTime)="row">
<b-form-input v-model="row.item.endTime" />
</template>
<template v-slot:cell(startTime)="row">
<b-form-input v-model="row.item.startTime" />
</template>
</b-table>
Where:
filtered = [ { "issueDescription": "this is a description", "endTime": "2020-02-11T14:00:00.000Z",
"startTime": "2020-02-11T01:24:00.000Z" }]
If I generate template with a v-for, then I got editable fields in every column, but no binding at each field.
<b-table striped hover :items="filtered">
<template v-for="x in filtered" v-slot:cell()="row">
<b-form-input v-model="row.item.BIND_TO_SPECIFIC_TABLE_ROW_COL" />
</template>
</b-table>
How do I bind to the specific row,col??
I've made a fiddle: https://jsfiddle.net/gfhu1owt/
If you want to have ALL fields editable, you can use this syntax.
<template v-slot:cell()="{ item, field: { key } }">
<b-form-input v-model="item[key]" />
</template>
It's pretty similar to what you had. You just needed to use the key from the slot context object. (I've shorthanded it a bit, but it's the same as going row.field.key).
Also note that i don't use a v-for in the template, this is because v-slot:cell() is a fallback slot which is valid for all slots unless a specific one is defined. For example v-slot:cell(issueDescription) would overwrite v-slot:cell() for the issueDescription field.
While the above works, the problem might come one day when you have a field you DONT want to be editable, like maybe an id field in your object.
To solve this issue, I've defined my fields and passed them to the table. I've also added a editable property to the fields i want to be editable. (note this is not a standard thing in the field object, but something specific for you use-case).
I then created a computed property editableFields which returns all fields that have editable: true, and then use editableFields in my v-for inside b-table.
This way you can pick and choose which properties you want to be editable in your objects.
new Vue({
el: "#app",
computed: {
editableFields() {
return this.fields.filter(field => field.editable)
}
},
data: {
filtered: [
{
"id": "1",
"issueDescription": "this is a description",
"endTime": "2020-02-11T14:00:00.000Z",
"startTime": "2020-02-11T01:24:00.000Z"
},
{
"id": "2",
"issueDescription": "this is a description",
"endTime": "2020-02-11T14:00:00.000Z",
"startTime": "2020-02-11T01:24:00.000Z"
}
],
fields: [
{ key: 'id', label: 'ID' },
{ key: 'issueDescription', label: 'Issue Description', editable: true },
{ key: 'startTime', label: 'Start Time', editable: true },
{ key: 'endTime', label: 'End Time', editable: true }
]
}
})
<link href="https://unpkg.com/bootstrap#4.4.1/dist/css/bootstrap.min.css" rel="stylesheet"/>
<link href="https://unpkg.com/bootstrap-vue#2.4.1/dist/bootstrap-vue.css" rel="stylesheet"/>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.6.10/vue.js"></script>
<script src="https://unpkg.com/bootstrap-vue#2.4.1/dist/bootstrap-vue.min.js"></script>
<div id='app'>
<b-table :items="filtered" :fields="fields">
<template v-for="field in editableFields" v-slot:[`cell(${field.key})`]="{ item }">
<b-input v-model="item[field.key]"/>
</template>
</b-table>
</div>

Removing last item when rendering in vue.js

I am facing an issue where a item is getting rendered even though there is no associated Title with it as shown in my JSON. Please see the attached screenshot which will make you understand my problem (marked in red). I know this is happening due to lid in my JSON for which vue is rendering that without any associated (i.e Title) values. How do I solve this issue. Is there a way to remove the last item when rendering or is there any other way ?. I need the lid in this.dino but do not need it when rendering in my vue-app. Is there a way to pop out the last item from the JSON when rendering.
<div id="vue-app">
<div id="myList" v-for="item in items">
<p>{{item.Title}}</p>
<button v-on:click="loadmore()" class="fluid ui button">Load More</button>
Below is my vue function
new Vue({
el: '#vue-app',
delimiters: ['[[', ']]'],
data: {
dino: d_var,
cati: d_catox,
items: []
},
methods: {
loadmore: function () {
axios.get(this.dino)
.then(response => {
this.items.push(...response.data);
this.dino = "/api/search/" + this.items[this.items.length - 1].lid + "/" + this.cati;
})
}
}
})
Below is my JSON
[
{
"Title": "HealthXP1"
},
{
"Title": "HealthXP2"
},
{
"Title": "HealthXP3"
},
{
"lid": "A1234567890"
}
]
you can use 'v-if' or 'v-show' directive as:
<div id="vue-app">
<div id="myList" v-for="item in items" v-show="item.Title">
<p>{{item.Title}}</p>
<button v-on:click="loadmore()" class="fluid ui button">Load More</button>
that will show that item on the list if the item.Title is defined, otherwise it will not be rendered in DOM

state of currentPage is changing after coming back to page in vue

I am struggling to maintain the state of current page in vuex. I am tring to store the state of pagination when I click on any record and the on click of back button I should get the same page.
Below is the code snippest :
table.html
<b-table show-empty
outlined
hover
stacked="md"
:sort-by.sync="sortBy"
:items="companies"
:fields="[
{key: 'name', label: this.$t('shared.name'), sortable: true},
{key: 'companyId', label: this.$t('shared.orgNumber'), sortable: true},
{key: 'properties.state', label: this.$t('shared.status'), sortable: true},
{key: 'serviceProviderName', label: this.$t('shared.serProvider'), sortable: true}
]"
:current-page="currentPage"
:per-page="perPage"
:filter="filter"
v-bind:empty-text='$t("shared.emptyText")'
v-bind:empty-filtered-text='$t("shared.emptyFilteredText")'
#filtered="onFiltered"
#row-clicked="showCompany"
tbody-tr-class="row-cursor">
</b-table>
<b-pagination :total-rows="totalRows" :per-page="perPage" v-model="currentPage"
class="float-right"/>
in js file I have added computed like this :
computed: {
currentPage:{
get () {
return this.$store.getters.currentPage;
},
set (value) {
this.$store.commit('SET_CURRENT_PAGE', value);
}
},
},
Now the problem is when I am click on page number 2 and then click on any record and on click of record, new page is opening and then when I come back from that page I am not getting page number as 2. It is changing again to 1. In developer tool I can see it is storing the state but it is chaning when I come back to the page
I dont know why the state of currentPage is changing. Please help!
currentPage will reset after you leave the page.
To prevent such a behavior you should open record page in new window/tab or save the currentPage value in cookies or localStorage.
If you prefer the latter I would advice you to use vuex-persistedstate
You should use #change attribute on your b-pagination component. If you bind it to a function returning items, it can use currentPage inside it to fetch the data for current page.
<b-pagination #change="fetchItems" ....>
Inside your component you could have a method
methods: {
fetchItems () {
// Use this.$store.getters.currentPage inside this function
// to fetch paginated data
return items;
}
}
You could also have a mount method to fetch paginated data using currentPage, this way you could fetch the data when you come back on page.
mounted () {
fetchItems()
}
The issue is when you leave the component it goes back to the ether. When you come back to that component, the page is built again. You should use the lifecycle hook created() to retrieve the saveed page from the store. That way the currentPage variable will have the page set prior to rendering all of the elements. It's probably better to use regular variable currentPage since there isn't a need for it to be a computed function.
You need to maintain a flag to achieve this using store/vuex.
Take a look at the code below. I hope this is something you were looking for
<b-table show-empty
outlined
hover
stacked="md"
:sort-by.sync="sortBy"
:items="companies"
:fields="[
{key: 'name', label: this.$t('shared.name'), sortable: true},
{key: 'companyId', label: this.$t('shared.orgNumber'), sortable: true},
{key: 'properties.state', label: this.$t('shared.status'), sortable: true}
]"
:current-page="currentPage"
:per-page="perPage"
:filter="filter"
v-bind:empty-text='$t("shared.emptyText")'
v-bind:empty-filtered-text='$t("shared.emptyFilteredText")'
#filtered="onFiltered"
#row-clicked="showCompany"
tbody-tr-class="row-cursor">
</b-table>
<b-pagination :total-rows="totalRows" :per-page="perPage" v-model="currentPage"
class="float-right"/>
data() {
return {
vuexStateApplied: false
}
}
methods: {
onFiltered(filteredItems) {
if (!this.vuexStateApplied && !this._.isEmpty(this.$store.getters.currentPage)) {
this.currentPage = this.$store.getters.currentPage
// By setting this flag we are ensuring not to
// apply same currentPage value again on
// filtering the table data
this.vuexStateApplied = true
}
}
}
beforeRouteLeave() {
// update with new value
this.$store.commit('SET_CURRENT_PAGE', value);
}
OR
destoryed() {
// update with new value
this.$store.commit('SET_CURRENT_PAGE', value);
}

VueJS v-if = array[index] is not working

I wanted to make a component which gets text-box when mouse is over the image.
Below is HTML template.
<section class="item-container" v-for="(item, index) in items">
<div class="image-box" #mouseenter="changeStatus(index)">
<img class="image" src="item.link" alt>
</div>
<div class="text-box" #mouseleave="changeStatus(index)" v-if="show[index]">
<h4>{{ item.name }}</h4>
<p>{{ item.content }}</p>
</div>
</section>
And below is app.js
new Vue({
el: '#app',
data: {
show: [false, false, false],
items: [
{
name: 'Author 1',
content: 'Content 1'
},
{
name: 'Author 2',
content: 'Content 2'
},
{
name: 'Author 3',
content: 'Content 3'
}
]
},
methods: {
changeStatus: function(index) {
this.show[index] = !this.show[index];
console.log(this.show);
console.log(this.show[index]); // console gets it as expected
}
}
});
When I execute above codes, I can find that the show property has changed. However, v-if is not updated. Can't we use array[index] for v-if or is there other reason for it?
The problem is not about v-if, it's because Vue cannot detect the changes of an array element directly, this is one of the limitation of JavaScript.
Thus, Vue provides some helper functions for this, like Vue.set
Change this this.show[index] = !this.show[index]
to Vue.set(this.show, index, !this.show[index])
then it should work.
Vue.set is not the only solution, there are several ways to accomplish this, in case you may want to know.
You can use native methods of JavaScript array, Vue will hook on these methods so it can detect the changes.
Here is the example of the usage of .splice
this.show.splice(index, 1, !this.show[index])
Another way is to replace the array entirely. If you are using ES6, you can use the spread operator to clone the array easily:
this.show[index] = !this.show[index]
this.show = [...this.show]
.map will also work because it returns a new array
this.show = this.show.map((el, i) =>
i === index ? !el : el
)
You can use a JS object instead of an array and get the same effect. In other words, replace show: [false, false, false], with show: {0:false, 1:false, 2:false},.
In component in methods you can use:
this.$set(this.show, index, !this.show[index])