How to dynamically add a column to Bootstrap Vue Table - javascript

I am using Bootstrap Vue and would like to add a column dynamically or only show the column if some condition is met in the data. I'm trying to understand scoped slots and whether this is the direction I should take with a formatter callback function. Ideally I would like to add a column "Version 2" only if it is available in the data. I'm not sure where to start.
Code
<b-table striped responsive hover :items="episodes" :fields="fields" :filter="filter">
<template v-slot:cell(version_1)="data">
<a :href="`${data.value.replace(/[^a-z]-/i,'-').toLowerCase()}`">Definition</a>
</template>
</b-table>
</template>
<script>
data() {
return {
fields: [{
key: 'category',
sortable: true
},
{
key: 'episode_name',
sortable: true
},
{
key: 'episode_acronym',
sortable: true
},
{
key: 'version_1',
sortable: true
}
],
episodes: [],
versions: [],
}
},
mounted() {
return Promise.all([
// fetch the owner of the blog
client.getEntries({
content_type: 'entryEpisodeDefinitions',
select: 'fields.title,fields.category,fields.slug,fields.episodeAcronym,fields.version,sys.id'
})
])
.then(response => {
// console.info(response[0].items);
return response[0].items
})
.then((response) => {
this.episodes = response.reduce(function( result, item){
if ( item.fields.version === 1 ) {
return result.concat({
category: item.fields.category,
episode_name: item.fields.title,
episode_acronym: item.fields.episodeAcronym,
version_1: 'episodes/' + item.fields.slug + '/' + item.sys.id,
})
}
return result
}, [])
this.versions = response.reduce(function( result, item){
if ( item.fields.version === 2 ) {
return result.concat({
version_2: 'episodes/' + item.fields.slug + '/' + item.sys.id,
})
}
return result
}, [])
console.info(response);
})
.catch(console.error)
},
</script>

I think this is something, you are looking for:
<b-table :items="items" :fields="fields">
<template v-for="field in dynamicFields" v-slot:[`cell(${field.key})`]="{ item }">
</b-table>
in scripts:
this.dynamicFields.push({key: 'someTestKey', label: 'Dynamic Label'})

Related

Vuetify/VueJS - Autocomplete results not showing until input is cleared

I'm at a loss here as to why this is happening.
When I type into the autocomplete input, my data gets fetched via axios fine, it populates "searchResults", but the results do not display until the input is cleared.
Autocomplete:
<v-slide-x-transition>
<v-autocomplete
:items="searchResults"
:loading="isSearching"
:search-input.sync="search"
color="primary"
hide-no-data
hide-selected
item-text="Description"
item-value="API"
label="Search"
placeholder="Start typing to Search"
prepend-icon="mdi-database-search"
return-object
class="ml-2 mt-2"
v-if="searchBox"
>
<template v-slot:item="data">
<v-list-item-avatar>
<v-icon>
mdi-account
</v-icon>
</v-list-item-avatar>
<v-list-item-content>
<v-list-item-title v-html="data.item.first_name"></v-list-item-title>
<v-list-item-subtitle v-html="data.item.last_name"></v-list-item-subtitle>
</v-list-item-content>
</template>
</v-autocomplete>
</v-slide-x-transition>
Code:
export default {
name: 'navBar',
data: () => ({
searchBox: true,
search: null,
select: null,
searchResults: [],
isSearching: false,
}),
watch: {
search(val) {
this.querySelections(val);
},
},
methods: {
showSearch() {
this.searchBox = true;
},
hideSearch() {
this.searchBox = false;
this.search = null;
this.searchResults = [];
},
async querySelections() {
this.isSearching = true;
const res = await this.callApi('get', '/app/search?query=' + this.search);
if (!res.data) {
this.searchResults = [];
} else {
this.searchResults = res.data;
}
console.log(this.searchResults);
this.isSearching = false;
}
},
mounted() {
}
}
Json:
[
{
"id":1,
"first_name":"Nigel",
"last_name":"Brian",
"email":"n.br#thes.co.uk",
"email_verified_at":"2021-12-02T00:00:00.000000Z",
"super_admin":1,
"profile_photo":"1642002293.jpg",
"created_at":null,
"updated_at":"2022-01-12T15:53:41.000000Z",
"deleted_at":null,
"permissions_for_vue":"{\"users_add\":true,\"users_view\":true,\"users_edit\":true,\"users_delete\":true,\"users_permissions\":true}",
"permissions_alt":{
"users_add":true,
"users_view":true,
"users_edit":true,
"users_delete":true,
"users_permissions":true
}
},
{
"id":2,
"first_name":"Jane",
"last_name":"Parker",
"email":"j.parks#test.co.uk",
"email_verified_at":"2021-12-02T00:00:00.000000Z",
"super_admin":0,
"profile_photo":"1641313913.jpg",
"created_at":null,
"updated_at":"2022-01-12T14:18:29.000000Z",
"deleted_at":null,
"permissions_for_vue":"{\"users_permissions\":true,\"users_add\":true,\"users_view\":true,\"users_edit\":true,\"users_delete\":true}",
"permissions_alt":{
"users_permissions":true,
"users_add":true,
"users_view":true,
"users_edit":true,
"users_delete":true
}
},
{
"id":5,
"first_name":"Nick",
"last_name":"Walters",
"email":"n.w#email.co.uk",
"email_verified_at":"2021-12-02T00:00:00.000000Z",
"super_admin":0,
"profile_photo":null,
"created_at":null,
"updated_at":"2022-01-12T15:27:22.000000Z",
"deleted_at":null,
"permissions_for_vue":"{\"users_permissions\":true,\"users_add\":true,\"users_view\":true,\"users_delete\":true,\"users_edit\":true}",
"permissions_alt":{
"users_permissions":true,
"users_add":true,
"users_view":true,
"users_delete":true,
"users_edit":true
}
},
{
"id":16,
"first_name":"Billy",
"last_name":"Brag",
"email":"234edgh#Erg.com",
"email_verified_at":null,
"super_admin":1,
"profile_photo":null,
"created_at":"2022-01-12T15:34:02.000000Z",
"updated_at":"2022-01-12T15:43:17.000000Z",
"deleted_at":null,
"permissions_for_vue":"[]",
"permissions_alt":[
]
}
]
Is there something obvious I'm missing here? Thanks for reading!
As I pointed out earlier in the comment, the problem is in item-value and item-text properties.
There are no Description and API fields in your JSON data, but these fields are specified in the template.
According to docs, these fields can contain string, array or function.
So you may change it to string value with name of field that should exist in your JSON:
...
item-text="first_name"
item-value="id"
...
or create a method with function that will return some combined value:
...
:item-text="getFullName"
item-value="id"
...
methods: {
...
getFullName(item) {
return [item.first_name, item.last_name].join(' ')
}
}
You may check this solution with static data at CodePen.

[Vue warn]: Error in v-on handler: "SyntaxError: Unexpected token , in JSON at position 76"

While I am trying to add this JSON data into Database then it is giving me the above Error!
var tax_setting = '{ "total_tax_settings": { "charge_tax": ' + this.taxInfoRadioButton +
', "tax_settings" : [' +
'{ "tax_name": ' + this.taxName +
', "tax_rate": ' + this.taxRate +
', "tax_included_in_price": ' + this.taxIncludeInPrice + ' } ]}}';
Radio Button of this JSON:
<v-radio-group v-model="taxInfoRadioButton">
<v-radio color="primary" label="Do not charge tax on purchases" value="1"></v-radio>
<v-radio color="primary" label="Charge tax on purchase" value="2"></v-radio>
</v-radio-group>
data() {
return {
taxInfoRadioButton: '1'
}
}
Text Field:
<v-text-field name="taxName" label="Tax Name" id="taxName" v-model="taxName"></v-text-field>
data() {
return {
taxName: ''
}
}
Tax Rate is also same as above.
Select Option in JSON:
<v-select hide-details v-bind:items="willTaxInclude" v-model="taxIncludeInPrice" label="Select" single-line menu-props="bottom" ></v-select>
data() {
return {
willTaxInclude: [
{ text: "Yes", value: 1 },
{ text: "No", value: 0 }
],
taxIncludeInPrice: '0',
}
}
Parse it in JSON format:
var tax_setting = JSON.parse(tax_setting);
I am passing this data to Database using axios
axios.post('/api/data/user/save-business-settings', {
tax_setting: tax_setting,
}).then((res) => {
console.log(res);
}).catch((err) => {
console.log(err);
})
What is the problem I can't figure out Actually. Please help me.
The number #1 rule to keep in mind when working with JSON is...
NEVER ROLL YOUR OWN JSON
JSON is best created by first creating the object you want to serialise, then passing that to JSON.stringify(). Since you're using Axios, you don't even have to do that since Axios does it for you; you simply pass it the object you want POSTed.
const tax_setting = {
total_tax_settings: {
charge_tax: this.taxInfoRadioButton,
tax_settings: [{
tax_name: this.taxName,
tax_rate: this.taxRate,
tax_included_in_price: this.taxIncludeInPrice
}]
}
}
axios.post('/api/data/user/save-business-settings', { tax_setting })
.then(...)

Extracting data from an API in typescript vue and to a prop

I need some help guys. Anytime I've done this I've used a v-for and extracted the data manually. The problem I am facing here is that the component I am passing the data to is a custom component already written to accept the data in an :options prop. But all I am getting is "null" for every piece of state.
Here is the template
<WDropDown label="Commissioner" :options="dropdownCommissionerValues" v-model="commissioner1"/>
And the script lang="ts"
// Data Section
apiInvoker= methods; //this is coming from a service file that logs to the API
public commissioner1 = "1";
dropdownCommissionerValues= [];
mounted () {
this.apiInvoker.viewRequestGet("resources/GetCommisionerList").then((response) => {
if(response.data) {
this.dropdownCommissionerValues = response.data.Table;
console.log("My Response is", this.dropdownCommissionerValues);
}
});
}
Inspecting the Vue Dev Tool, and the console, I am connecting to the API and I am getting data in the component. See image below
But this is what I am getting in the browser See image
NOTE, If I hardcode my data in dropdownCommissionerValues as shown below, it get it to work. But I need it to come from the API
this.dropdownCommissionerValues= [
{
value:"1",
text:"Something 1"
},
{
value:"2",
text:"Something 2"
}
]
I assume somehow I have to pass CommissionerId as value and CommissionerName as text because that's how the component reads it? I just can't figure out how
Thanks in advance!
Here is the custom component
<template>
<ValidationProvider :vid="id" :name="id" :rules="rules" v-slot="wDropDown">
<wFormGroup :class="classObject"
:id="groupId('lbl', id)"
:label="label"
:label-for="id"
:label-sr-only="labelSrOnly">
<template v-if="(pagePlaintext || plaintext || isPlaintext) && !dropDownBind">
<b-form-input plaintext
aria-readonly="true"
readonly="readonly"
:aria-label="label"
:data-vv-as="label"
:id="id"
:name="name"
:type="type"
:value="refDataValue" />
</template>
<template v-else-if="dropDownBind">
<div :class="divClass">
<b-form-select class="attachedSelect"
:id="id"
:aria-label="label"
:aria-required="required"
:data-vv-as="label"
:data-vv-name="label"
:data-vv-scope="scope"
:class="{ 'is-invalid': wDropDown.errors.length > 0 }"
:disabled="disabled || arcDisabledObj"
:label="label"
:multiple="multiple"
:name="name"
:options="options"
:select-size="selectSize"
:text-field="selectText"
v-model="computedVal"
:value-field="selectValue"
#input="onInput">
<template slot="first" v-if="!(multiple) && displayFirstSlot">
<option v-if="customFirstSlotValue !== null"
:value="customFirstSlotValue">
{{placeholderText}}
</option>
<option v-else-if="computedVal !==null && computedVal !== undefined && computedVal.length ===0"
computedVal>
{{placeholderText}}
</option>
<option v-else :value="null">{{placeholderText}}</option>
</template>
</b-form-select>
</div>
</template>
<template v-else>
<div :class="divClass">
<b-form-select class="form-control"
:id="id"
:aria-label="label"
:aria-required="required"
:data-vv-as="label"
:data-vv-name="label"
:data-vv-scope="scope"
:class="{ 'is-invalid': wDropDown.errors.length > 0 }"
:disabled="disabled || arcDisabledObj"
:label="label"
:multiple="multiple"
:name="name"
:options="options"
v-model="computedVal"
:select-size="selectSize"
:text-field="selectText"
:value-field="selectValue"
#input="onInput">
<template slot="first" v-if="!(multiple) && displayFirstSlot">
<option v-if="customFirstSlotValue !== null"
:value="customFirstSlotValue">
{{placeholderText}}
</option>
<option v-else-if="computedVal !==null && computedVal !== undefined && computedVal.length ===0"
computedVal>
{{placeholderText}}
</option>
<option v-else :value="null">{{placeholderText}}</option>
</template>
</b-form-select>
</div>
</template>
<b-form-text v-if="helpText" :id="groupId('hlp', id)">{{helpText}}</b-form-text>
<div class="invalid-feedback" v-if="wDropDown.errors.length > 0">{{ Message }}</div>
<!-- {{ isRequired && (!plaintext && !pagePlaintext) }} {{ isRequired }} {{ plaintext}} {{ overridePageMode }} -->
</wFormGroup>
</ValidationProvider>
</template>
<script>
import uuid from "uuid/v1";
import { groupId } from "#/helpers";
import { classObject } from "#/computed";
import { pagePlaintext } from "#/mixins";
/*
[DEPRECATED]: Left in to support Organization page. Do not use otherwise.
This should be removed in the future.
*/
import { arcInputStateMixin } from "#/components/mixins/UI/inputStateMixin";
export default {
data () {
return {
scope: uuid(),
Message: 'This field is required.',
};
},
methods: {
groupId,
onInput (value) {
this.$emit("input", value);
},
getSelectedDataValue (options, keyValue, keyField, valueField) {
let result = null;
let response = null;
console.log(options);
console.log(keyValue);
console.log(keyField);
console.log(valueField);
console.log(keyValue !== undefined && keyValue !== null);
if(keyValue !== undefined && keyValue !== null) {
response = options.filter(option => option[keyField].toString() === keyValue.toString());
}
console.log('wDropDown', this.id);
console.log(response);
if (this.isNotNullOrUndefined(response)) {
result = response[0][valueField] || null;
}
return result !== null ? result : this.readOnlyValue;
},
},
mixins: [arcInputStateMixin, pagePlaintext],
model: {
event: "input",
prop: "value",
},
name: "wDropDown",
props: {
isRequired: Boolean,
dropDownBind: {
type: Boolean,
default: false,
},
disabled: {
type: Boolean,
},
displayonly: {
type: String,
},
helpText: {
type: String,
},
id: {
required: true,
type: String,
},
inputState: {
type: String,
},
label: {
required: true,
type: String,
},
labelFor: {
type: String,
},
labelSrOnly: {
type: Boolean,
},
maxlength: {
type: String,
},
multiple: {
type: [String, Boolean],
default: false,
},
name: {
type: String,
},
options: {
default () {
return [];
},
type: [Array, String],
},
placeholderText: {
type: String,
default: "- Select One -",
},
plaintext: {
type: Boolean,
},
required: {
type: Boolean,
},
selectSize: {
type: String,
},
selectText: {
type: String,
default: "text",
},
selectValue: {
type: String,
default: "value",
},
type: {
type: String,
},
value: {
type: [Number, String, Array],
default: null,
},
validation: {
type: String,
},
customFirstSlotValue: {
type: [String, Number],
default: null,
},
displayFirstSlot: {
type: [String, Boolean],
default: true,
},
lookUpTableName: {
type: String,
},
overridePageMode: {
type: Boolean,
default: false,
},
readOnlyValue: {
type: String,
},
},
computed: {
rules () {
return (this.isRequired && (!this.plaintext && !this.pagePlaintext) ) ? "required" : "";
},
computedVal: {
get () {
return this.value;
},
set (val) {
return val;
},
},
classObject,
refDataValue () {
return this.getSelectedDataValue(
this.options,
this.computedVal,
this.selectValue,
this.selectText,
);
},
divClass () {
return this.multiple
? "multiSelect"
: (this.disabled || this.arcDisabledObj) === true
? "disable"
: "autofill";
},
},
beforeDestroy () {
//this.$store.commit("Common/SET_ADDRESS_MIXIN_COUNT", 0);
},
};
</script>
The custom component has selectValue and selectText props, which allows setting the b-form-select's value-field and text-field, respectively. You could set those to the desired values without having to remap your data:
<WDropDown :options="dropdownCommissionerValues"
selectText="CommissionerName"
selectValue="CommissionerId"
/>
demo
While not familiar with boostrapVue you could add keys (text and value) either after api call or from the component and using a computed.
Something like:
this.dropdownCommissionerValues = response.data.Table.map(option=>{
option.text=option.commissionerName
option.value=option.commissionerValue
delete option.commissionerName // optional .... clean up the object
delete option.commissionerValue // optional .... clean up the object
return option
})

Vue JS nested loop search not returning results

I'm building a key-command resource and giving VueJS a whirl while doing so. I'm a newbie but am gaining the grasp of things (slowly...).
I want to be able to search in a global search form for key commands I'm defining as actions within sections of commands (see data example below). I would like to search through all the actions to show only those that match the search criteria.
My HTML is below:
<div id="commands">
<input v-model="searchQuery" />
<div class="commands-section" v-for="item in sectionsSearched"
:key="item.id">
<h3>{{ item.section }}</h3>
<div class="commands-row" v-for="command in item.command" :key="command.action">
{{ command.action }}
</div>
</div>
</div>
My main Vue instance looks like this:
import Vue from 'vue/dist/vue.esm'
import { commands } from './data.js'
document.addEventListener('DOMContentLoaded', () => {
const element = document.getElementById("commands")
if (element != null) {
const app = new Vue({
el: element,
data: {
searchQuery: '',
commands: commands
},
computed: {
sectionsSearched() {
var self = this;
return this.commands.filter((c) => {
return c.command.filter((item) => {
console.log(item.action)
return item.action.indexOf(self.searchQuery) > -1;
});
});
},
}
});
}
});
And finally the data structure in data.js
const commands = [
{
section: "first section",
command: [
{ action: '1' },
{ action: '2' },
{ action: '3' },
],
},
{
section: "second section",
command: [
{ action: 'A' },
{ action: 'B' },
{ action: 'C' },
]
},
]
export { commands };
I'm able to output the commands using the console.log(item.action) snippet you see in the computed method called sectionsSearched.
I see no errors in the browser and the data renders correctly.
I cannot however filter by searching in real-time. I'm nearly positive it's a combination of my data structure + the computed method. Can anyone shed some insight as to what I'm doing wrong here?
I'd ideally like to keep the data as is because it's important to be sectioned off.
I'm a Rails guy who is new to this stuff so any and all feedback is welcome.
Thanks!
EDIT
I've tried the proposed solutions below but keep getting undefined in any query I pass. The functionality seems to work in most cases for something like this:
sectionsSearched() {
return this.commands.filter((c) => {
return c.command.filter((item) => {
return item.action.indexOf(this.searchQuery) > -1;
}).length > 0;
});
},
But alas nothing actually comes back. I'm scratching my head hard.
There is a issue in your sectionsSearched as it is returning the array of just commands.
See this one
sectionsSearched() {
return this.commands.reduce((r, e) => {
const command = e.command.filter(item => item.action.indexOf(this.searchQuery) > -1);
const section = e.section;
r.push({
section,
command
});
}, []);
}
const commands = [
{
section: "first section",
command: [
{ action: '1' },
{ action: '2' },
{ action: '3' },
],
},
{
section: "second section",
command: [
{ action: 'A' },
{ action: 'B' },
{ action: 'C' },
]
},
]
const element = document.getElementById("commands")
if (element != null) {
const app = new Vue({
el: element,
data: {
searchQuery: '',
commands: commands
},
computed: {
sectionsSearched() {
var self = this;
return this.commands.filter((c) => {
// the code below return an array, not a boolean
// make this.commands.filter() not work
// return c.command.filter((item) => {
// return item.action.indexOf(self.searchQuery) > -1;
// });
// to find whether there has command action equal to searchQuery
return c.command.find(item => item.action === self.searchQuery);
});
},
}
});
}
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<div id="commands">
<input v-model="searchQuery" />
<div class="commands-section" v-for="item in sectionsSearched"
:key="item.id">
<h3>{{ item.section }}</h3>
<div class="commands-row" v-for="command in item.command" :key="command.action">
{{ command.action }}
</div>
</div>
</div>
Is that work as you wish ?
sectionsSearched() {
return this.commands.filter((c) => {
return c.command.filter((item) => {
return item.action.indexOf(this.searchQuery) > -1;
}).length > 0;
});
},
}
since filter will always return an array(empty or not) which value always is true.

How do I reference data in my firebase child nodes?

I'm trying to reference child nodes of a firebase database using a for loop. I'm using Vue and Vue-fire. The problem is that in the following code, categories[addItem.category] doesn't reference the correct data.
<el-select v-model="addItem.category" placeholder="Select a category." id="category-select">
<el-option
v-for="(val, key) in categories"
:key="val['.key']"
:label="val['.key']"
:value="val['.key']">
</el-option>
</el-select>
<el-button #click="showAdd=true">new category</el-button>
<el-select v-model="addItem.subcategory" placeholder="Select a subcategory." id="subcategory-select" v-show="addItem.category!=''">
<el-option
v-for="subcat in categories[addItem.category]"
:key="subcat['.key']"
:label="subcat['.key']"
:value="subcat">
</el-option>
</el-select>
<el-button v-show="addItem.category!=''" #click="showAdd=true">add subcategory</el-button>
I am trying to get all the keys of the selected category, which should give 'machinery' if 'mechanical' is selected. Currently, selecting 'mechanical' makes categories[addItem.category] equal to undefined. Here is what the data looks like in my db:
[![enter image description here][1]][1]
Can anyone tell me why my firebase reference categories[addItem.category] isn't referencing the child values?
If it helps, this is how I save the data:
methods: {
add(){
var updates = {};
if (this.addItem.category==''){
updates['/' + this.addItem.text + '/'] = "null";
}
else if (this.addItem.subcategory==''){
console.log('adding a subcategory: ' + this.addItem.subcategory);
console.log(this.addItem.category + ' is the category name');
updates['/' + this.addItem.category + '/' + this.addItem.text + '/'] = "null";
}
db.ref('categories').update(updates).then((successMessage) => {
// successMessage is whatever we passed in the resolve(...) function above.
// It doesn't have to be a string, but if it is only a succeed message, it probably will be.
console.log("Yay! " + successMessage);
});
Which references the following data:
data(){ return {
addItem: {
open: false,
count: 0,
category: '',
subcategory: '',
kks: {
name: ''
},
document: {
name: ''
},
product: {
name: ''
},
text: ''
},
}}
}
and here is the firebase property:
firebase: function(){
return {
categories: db.ref('categories')
}
}
[1]: https://i.stack.imgur.com/hYZN6.png
Here's what I ended up doing. The following was added as a computed property:
subcategories: function(){
if (this.addItem.category){
var val;
db.ref('categories/' + this.addItem.category).on('value', function(snapshot){
val = snapshot.val();
});
return val;
}
return this.categories;
}

Categories

Resources