Error using template slot and slot-scope on VueJS - javascript

Error using a slot and slot-scope on VueJS to put into a column of a table.
A template as follows:
<b-table hover striped :items="users" :fields="fields">
<template slot="actions" slot-scope="data">
<b-button variant="warning" #click="loadUser(data.item)" class="mr-2">
<i class="fa fa-pencil"></i>
</b-button>
<b-button variant="danger" #click="loadUser(data.item, 'remove')">
<i class="fa fa-trash"></i>
</b-button>
</template>
</b-table>
After all, I'm able to get users on DB. And have success to show it on table. But, the buttons on slot can't.
The idea is put two buttons:
The update button
The delete button
And each button manage one function.
<script>
import { baseApiUrl, showError } from '#/global'
import axios from 'axios'
export default {
name: 'UserAdmin',
data: function(){
return {
mode: 'save',
user: {},
users: [],
fields: [
{ key: 'id', label: 'Código', sortable: true},
{ key: 'name', label: 'Nome', sortable: true},
{ key: 'email', label: 'E-mail', sortable: true},
{ key: 'adminMaster', label: 'Administrador', sortable: true,
formatter: value => value ? 'Sim' : 'Não'},
{key: 'adminEnterprise', label: 'Chefe de Empreendimento', sortable: true,
formatter: value => value ? 'Sim' : 'Não'},
{ key: 'manager', label: 'Gerente', sortable: true,
formatter: value => value ? 'Sim' : 'Não'},
{ key: 'customer', label: 'Cliente', sortable: true,
formatter: value => value ? 'Sim' : 'Não'},
{ key: 'actions', label: 'Ações'}
],
}
},
methods: {
loadUsers() {
const url = `${baseApiUrl}/users`
axios.get(url).then(res => {
this.users = res.data
})
},
reset() {
this.mode = 'save'
this.user = {}
this.loadUsers()
},
save() {
const method = this.user.id ? 'put' : 'post'
const id = this.user.id ? `/${this.user.id}` : ''
axios[method](`${baseApiUrl}/users${id}`, this.user)
.then(() => {
this.$toasted.global.defaultSuccess()
this.reset()
})
.catch(showError)
},
remove() {
const id = this.user.id
axios.delete(`${baseApiUrl}/users/${id}`)
.then(() => {
this.$toasted.global.defaultSuccess()
this.reset()
})
.catch(showError)
},
loadUser(user, mode = 'save') {
this.mode = mode
this.user = { ...user }
}
},
mounted() {
this.loadUsers()
}
}
</script>

Alright, so, it looks like you're using an official release of BV 2.0., BV is preparing for Vue 3 which is revising the component-slotting system.
As such, BV has renamed the custom slots, you need to use cell({key}) like so:
<template slot="cell(actions)" slot-scope="data">

Related

How can I use the defined filter function inside of an object? | Nuxt

I have a filter dropdown in the header and when users apply the filters I want to run the assigned function according to their choices but I couldn't run it, I got "... is not a function" error.
Also, even if I solve this problem, I will probably have problems in applying more than one filter at the same time, I would be very grateful if anyone could offer a solution in the form of applying filters in multiple ways.
Example Object:
const filters = [
{
section: 'Call Types',
slug: 'call-types',
type: 'multiselect',
options: [
{
label: 'Inbound',
slug: 'inbound',
value: true,
func: (data) =>
data.items.filter((item) => {
return item.className === 'ib'
}),
},
{
label: 'Outbound',
slug: 'outbound',
value: true,
func: (data) =>
data.items.filter((item) => {
return item.className === 'ob'
}),
},
],
},
{
section: 'Data Types',
slug: 'data-types',
type: 'multiselect',
options: [
{
label: 'Sentiment',
slug: 'sentiment',
value: false,
func: (data) =>
data.items.filter((item) => {
return item.d1 === 1
}),
},
{
label: 'Capture Fails',
slug: 'capture-fails',
value: false,
func: (data) =>
data.items.filter((item) => {
return item.d2 > 0.5
}),
},
{
label: 'Audio Notes',
slug: 'audio-notes',
value: false,
func: (data) =>
data.items.filter((item) => {
return item.d1 === 1 && item.d2 > 0.5
}),
},
],
},
{
section: 'Others',
slug: 'others',
type: 'singleselect',
options: [
{
label: 'Show All Agents',
slug: 'show-all-agents',
value: true,
func: (data) =>
data.items.filter((item) => {
return item
}),
},
],
},
]
exports.filters = filters
And here is the method I'm trying to run when users click to "Filter" button.
Timeline vue file:
<template>
<vis-timeline
:groups="filteredData.groups"
:items="filteredData.items"
/>
</template>
<script>
import { mapActions, mapState } from 'vuex'
import VisTimeline from '~/components/UI/VisTimeline.vue'
export default {
components: { VisTimeline },
computed: {
...mapState({
callTimeline: (state) => state.agents.callTimeline,
timelineLoading: (state) => state.agents.loading,
filterOption: (state) => state.agents.filterOption,
}),
filteredData() {
var filtered = Object.assign(
{},
JSON.parse(JSON.stringify(this.callTimeline))
)
return this.filterItems(filtered)
},
},
methods: {
filterItems(data) {
if (this.filterOption) {
this.filterOption.forEach((filter) => {
filter.options.some((option) => {
if (option.value) {
return option.func(data)
}
})
})
}
return data
}
}
}
</script>
Header vue file:
<template>
<a-layout-header style="background: #fff; padding: 0">
<a-dropdown
:trigger="['click']"
:visible="visible"
#visibleChange="handleVisible"
>
<template #overlay>
<a-menu>
<a-menu-item-group
v-for="filter in filters"
:key="filter.slug"
:title="filter.section"
>
<a-menu-item :key="filter.slug">
<a-checkbox
v-for="option in filter.options"
:key="option.slug"
:name="option.slug"
:checked="option.value"
#change="filterChanges"
>
{{ option.label }}
</a-checkbox>
</a-menu-item>
</a-menu-item-group>
<a-menu-divider />
<a-menu-item key="buttons"
><div class="flex justify-between">
<a-button #click="visible = false" type="danger" ghost
>Cancel</a-button
>
<a-button #click="applyFilters" type="primary">Filter</a-button>
</div></a-menu-item
>
</a-menu>
</template>
<div class="mb-1">
<a-button type="primary">
<div class="flex flex-row justify-center space-x-2 items-center">
<a-icon type="filter" />
<span>Filters</span>
<a-icon type="down" />
</div>
</a-button>
</div>
</a-dropdown>
</a-layout-header>
</template>
<script>
import { mapActions} from 'vuex'
import { filters } from '~/utils/Filters'
export default {
name: 'DBHeader',
data() {
return {
visible: false,
filters: filters,
}
},
methods: {
...mapActions({
setFilterOption: 'agents/setFilterOption',
}),
handleVisible(flag) {
this.visible = flag
},
filterChanges(e) {
this.filters.forEach((filter) => {
filter.options.some((option) => {
if (option.slug === e.target.name) {
option.value = e.target.checked
}
})
})
},
applyFilters() {
var filters = Object.assign([], JSON.parse(JSON.stringify(this.filters)))
this.setFilterOption(filters)
this.visible = false
},
},
}
</script>

make whole row clickable

i'm new on react(hooks) typescript, trying to learn by doing,
here i have created antd table(which works), on the right side of table is 'Edit' it is clickable and works well, but my question is how to make each row clickable instead of that 'Edit' ? like i could click anywhere on the row and it should take me to its 'Edit' link instead of me clicking just on 'Edit':
import { useTranslation } from "react-i18next";
const { t } = useTranslation();
const columns = [
{
title: t("tilaus.state"),
dataIndex: "state",
key: "state",
render: (value: OrderState) => (
<span className="material-icons">
</span>
),
},
{
title: t("request.parcelId"),
dataIndex: "parcelId",
key: "parcelId",
},
{
title: t("request.date"),
dataIndex: "date",
key: "date",
render: (value: Date) => <div>{value.toLocaleDateString("af-AF")}</div>,
},
{
title: t("request.sender"),
dataIndex: "sender",
key: "sender",
render: (value: CustomerDto) => <div>{value.name}</div>,
},
{
title: t("request.recipient"),
dataIndex: "recipient",
key: "recipient",
render: (value: CustomerDto) => <div>{value.name}</div>,
},
{
title: t("request.price"),
dataIndex: "price",
key: "price",
},
{
title: "",
dataIndex: "id",
key: "id",
render: (value: string) => (
<Link to={"details/" + value}>{t("request.edit")}</Link>
),
},
];
<Table
dataSource={orders}
columns={columns}
pagination={false}
scroll={{ x: true }}
onRow={(record, rowIndex) => {
return {
onClick: (event) => {
handleClick(record);
},
};
}}
/>
Looks like you are using ant.d table. I've grabbed one of the examples and console logged the output. You can find it here: https://codesandbox.io/s/s1xds?file=/index.js to show you how the onclick in the entire row is being triggered.
For your specific thing, you need to change onRow prop. You can't add a Link directly to the row, but you can use history from react-router (if you are using it, docs here) or directly mutate the URL when onclick is called.
So in your case, you'll have to do this:
<Table
dataSource={orders}
columns={columns}
pagination={false}
scroll={{ x: true }}
onRow={(record, rowIndex) => {
return {
onClick: (event) => {
history.push(`/details/${record.id}`);
},
};
}}
/>
or
<Table
dataSource={orders}
columns={columns}
pagination={false}
scroll={{ x: true }}
onRow={(record, rowIndex) => {
return {
onClick: (event) => {
window.location.href = `${window.location.href}/details/${record.id}`
},
};
}}
/>

v-model is not changing data with switch and select dropdown in vuejs

I'm making a dynamic admin panel with crud generator with the help of laravel and vue.
I have a table where I'm loading data asynchronously from API. There is an is_featured column in my table which I want to be a switch. So that the user can change the value from the table page instead of going to edit page.
To generate my entire table there is a configuration object that contains which fields to show and the type of that field and other metadata. In the configuration object, there is a field named prerender which is responsible to prerender fields that require calling other API or some editable fields like select dropdown or switch.
To make switch and select fields work, I have an empty object named fieldData.
When a field with type: boolean and prerender: true is found, my code will initialize a property with field.name as property name in field data and fill it with the corresponding values under that field name
this.fieldData[field.name] = {};
this.tableData.forEach(
data => (this.fieldData[field.name][data.id] = data[field.name])
);
BUT THE SWITCH AND SELECT ARE NOT WORKING HERE
So I need help.
Here's my entire code for reference
<template>
<div class="app-container">
<el-row :gutter="20" style="display: flex; align-items: center;">
<el-col :span="10">
<h1 style="text-transform: capatilize;">{{ resourceName }}</h1>
</el-col>
<el-col :span="14" style="display: flex; justify-content: flex-end; align-items: center">
<el-input
v-model="navigation.search"
placeholder="Search anything here"
prefix-icon="el-icon-search"
style="width: 300px; margin: 0 10px;"
#keydown.enter.native="handleGlobalSearch"
/>
<FilterPannel
style="margin: 0 10px"
:filter-pannel-obj="filterPannelObj"
#set-filter="handleFilterVals"
#reset-filter="getTableData({})"
/>
<Import
:url="`/api/${resourceName}/upload`"
#import-success="handleImportSucces"
#import-error="handleImportError"
/>
<Export :url="`/api/${resourceName}/export`" :selected-ids="selected.map(el => el.id)" />
<el-button
type="info"
icon="el-icon-delete"
#click="$refs['table'].clearSelection()"
>Clear Selection</el-button>
<el-button type="danger" icon="el-icon-delete" #click="handleMultipleDelete">Delete Selected</el-button>
</el-col>
</el-row>
<el-row>
<el-table
ref="table"
v-loading="loading.tableData"
:data="tableData"
border
:row-key="getRowKeys"
#sort-change="handleSortChange"
#selection-change="handleSelectionChange"
>
<el-table-column type="selection" label="Selection" reserve-selection />
<el-table-column label="Actions" width="200">
<template slot-scope="scope">
<div style="display: flex; justify-content: space-around;">
<el-button
icon="el-icon-view"
type="primary"
circle
#click="$router.push(`/${resourceName}/view/${scope.row.id}`)"
/>
<el-button
icon="el-icon-edit"
type="success"
circle
#click="$router.push(`/${resourceName}/edit/${scope.row.id}`)"
/>
<el-button
icon="el-icon-delete"
type="danger"
circle
#click="handleDeleteClick(scope.row.id)"
/>
</div>
</template>
</el-table-column>
<el-table-column
v-for="field in fieldsToShow"
:key="field.name"
:prop="field.name"
:label="field.name.replace('_',' ')"
sortable="custom"
>
<template slot-scope="scope">
<div
v-if="field.type=='multilangtext'"
class="cell"
>{{ JSON.parse(scope.row[field.name])[$store.state.app.language] }}</div>
<div v-if="field.type=='text'" class="cell">{{ scope.row[field.name] }}</div>
<el-tag v-if="field.type=='tag'">{{ scope.row.type }}</el-tag>
<img v-if="field.type=='image'" :src="scope.row.icon" width="100px" height="100px" />
<div v-if="field.type=='oneFrom'" class="cell">
<el-tag
:key="scope.row[field.name]"
>{{field.multilang ? scope.row[field.name][$store.state.app.language] : scope.row[field.name]}}</el-tag>
</div>
<div v-if="field.type=='manyFrom'" class="cell">
<el-tag
style="margin: 5px"
v-for="(item, index) in scope.row[field.name]"
:key="`${field.multilang ? item[$store.state.app.language] : item}+${index}`"
>{{field.multilang ? item[$store.state.app.language] : item}}</el-tag>
</div>
<div v-if="field.type=='boolean'" class="cell">
{{scope.row.id}} =>
{{field.name}} =>>
{{scope.row[field.name]}} =>>>
{{fieldData[field.name][scope.row.id]}}
<el-switch
:key="`switch${scope.row.id}`"
v-model="fieldData[field.name][scope.row.id]"
:active-value="1"
:inactive-value="0"
></el-switch>
</div>
<div v-if="field.type=='select'" class="cell">
<el-select :key="`select${scope.row.id}`" v-model="fieldData[field.name][scope.row.id]" placeholder="Select">
<el-option
v-for="item in field.options"
:key="item"
:label="item"
:value="item"
></el-option>
</el-select>
</div>
</template>
</el-table-column>
</el-table>
</el-row>
<el-row>
<pagination
style="padding: 0;"
:total="paginationData.total"
:page.sync="paginationData.current_page"
:limit.sync="paginationData.per_page"
#pagination="handlePagination"
/>
</el-row>
</div>
</template>
<script>
import Pagination from '#/components/Pagination';
import FilterPannel from '#/components/FilterPannel';
import Export from './components/Export'; // ? not needed
import Import from './components/Import';
import axios from 'axios';
import Resource from '#/api/resource';
const resourceName = 'coupons';
const ResourceApi = new Resource(resourceName);
export default {
name: 'CategoryList',
components: {
Pagination,
FilterPannel,
Export,
Import,
},
data() {
return {
resourceName: resourceName,
language: 'en',
tableData: [],
fieldData: {},
fieldsToShow: [
{ name: 'title', type: 'multilangtext' },
// { name: 'description', type: 'multilangtext' },
{ name: 'code', type: 'text' },
// { name: 'expiry_date', type: 'text' },
{ name: 'type', type: 'tag' },
{
name: 'store_id',
type: 'oneFrom',
url: '/api/stores/',
foreignKey: 'store_id',
attrName: 'name',
multilang: true,
prerender: true,
},
{
name: 'tags',
type: 'manyFrom',
url: '/api/tags?idsarr=',
foreignKey: 'tags',
attrName: 'name',
multilang: true,
prerender: true,
},
{
name: 'brands',
type: 'manyFrom',
url: '/api/brands?idsarr=',
foreignKey: 'brands',
attrName: 'name',
multilang: true,
prerender: true,
},
// { name: 'brands', type: 'text' },
{ name: 'is_featured', type: 'boolean', prerender: true },
{
name: 'status',
type: 'select',
options: ['publish', 'draft', 'trash'],
prerender: true,
},
],
paginationData: {
current_page: 0,
last_page: 0,
per_page: 0,
total: 0,
},
navigation: {
page: 1,
limit: 10,
sort: '',
'sort-order': 'asc',
filters: '',
search: '',
},
filters: '',
selected: [], // ? for selection
loading: {
tableData: false,
},
allData: [],
filterPannelObj: {
title: {
default: '',
type: 'Input',
label: 'Title',
},
description: {
default: '',
type: 'Input',
label: 'Description',
},
promo_text: {
default: '',
type: 'Input',
label: 'Promo Text',
},
type: {
default: [],
type: 'checkbox',
label: 'Type',
src: [
{ value: 'coupon', label: 'Coupon' },
{ value: 'offer', label: 'Offer' },
],
},
code: {
default: '',
type: 'Input',
label: 'Code',
},
store_id: {
default: [],
type: 'select',
label: 'Store',
src: [],
multiple: true,
},
brands: {
default: [],
type: 'select',
label: 'Brands',
src: [],
multiple: true,
},
tags: {
default: [],
type: 'select',
label: 'Tags',
src: [],
multiple: true,
},
cats: {
default: [],
type: 'select',
label: 'Categories',
src: [],
multiple: true,
},
},
};
},
watch: {
async 'navigation.search'(newVal, oldVal) {
await this.handleGlobalSearch();
},
},
async created() {
await this.getTableData({});
this.allData = await ResourceApi.list({ limit: -1 });
// to fill filter dialog selects
// To get brands
this.filterPannelObj.brands.src = (await axios.get(
`/api/brands?limit=-1`
)).data.map(({ name, id }) => ({
label: JSON.parse(name)[this.$store.state.app.language],
value: id,
}));
// To get tags
this.filterPannelObj.tags.src = (await axios.get(
`/api/tags?limit=-1`
)).data.map(({ name, id }) => ({
label: JSON.parse(name)[this.$store.state.app.language],
value: id,
}));
// To get categories
this.filterPannelObj.cats.src = (await axios.get(
`/api/categories?limit=-1`
)).data.map(({ name, id }) => ({
label: JSON.parse(name)[this.$store.state.app.language],
value: id,
}));
// To get stores
this.filterPannelObj.store_id.src = (await axios.get(
`/api/stores?limit=-1`
)).data.map(({ name, id }) => ({
label: JSON.parse(name)[this.$store.state.app.language],
value: id,
}));
},
methods: {
printScope(x) {
console.log('TCL: printScope -> x', x);
},
async getTableData(query) {
this.loading.tableData = true;
const responseData = await ResourceApi.list(query);
this.tableData = responseData.data;
this.paginationData = this.pick(
['current_page', 'last_page', 'per_page', 'total'],
responseData
);
Object.keys(this.paginationData).forEach(
key => (this.paginationData[key] = parseInt(this.paginationData[key]))
);
await this.handlePrerender();
this.loading.tableData = false;
},
async handlePrerender() {
this.fieldsToShow.forEach(async field => {
if (field.prerender) {
switch (field.type) {
case 'oneFrom': {
await this.setRelatedFieldName(field);
break;
}
case 'manyFrom': {
await this.setRelatedFieldName(field);
break;
}
case 'boolean': {
this.fieldData[field.name] = {};
this.tableData.forEach(
data => (this.fieldData[field.name][data.id] = data[field.name])
);
break;
}
case 'select': {
this.fieldData[field.name] = {};
this.tableData.forEach(
data => (this.fieldData[field.name][data.id] = data[field.name])
);
break;
}
}
}
});
},
// utils
pick(propsArr, srcObj) {
return Object.keys(srcObj).reduce((obj, k) => {
if (propsArr.includes(k)) {
obj[k] = srcObj[k];
}
return obj;
}, {});
},
// ? remember to refactor the parameter id
async setRelatedFieldName({
name,
type,
url,
foreignKey,
attrName,
multilang,
}) {
this.tableData.forEach(async data => {
if (type === 'oneFrom') {
data[name] = (await axios.get(`${url}${data[foreignKey]}`)).data[
attrName
];
if (multilang) {
data[name] = JSON.parse(data[name]);
}
} else if (type === 'manyFrom') {
data[name] = (await axios.get(`${url}${data[foreignKey]}`)).data.map(
idata => (multilang ? JSON.parse(idata[attrName]) : idata[attrName])
);
}
});
},
// Sort
async handleSortChange(change) {
this.navigation.sort = change.prop;
if (change.order === 'ascending') {
this.navigation['sort-order'] = 'asc';
} else if (change.order === 'descending') {
this.navigation['sort-order'] = 'desc';
}
await this.getTableData(this.navigation);
},
// Pagination
async handlePagination(obj) {
// obj page obj containing {page: ..., limit: ...}
this.navigation.page = obj.page;
this.navigation.limit = obj.limit;
await this.getTableData(this.navigation);
},
// Global Search
async handleGlobalSearch() {
await this.getTableData(this.navigation);
},
// ? Skipped for now
// Filters
async handleFilterVals(filterparams) {
console.log('TCL: handleFilterVals -> filterparams', filterparams);
this.navigation.filters = JSON.stringify(filterparams.filters);
this.navigation.sort = filterparams.sort.field;
this.navigation.sort = 'name';
this.navigation['sort-order'] = filterparams.sort.asc ? 'asc' : 'desc';
await this.getTableData(this.navigation);
},
async handleDeleteClick(id) {
ResourceApi.destroy(id)
.then(res => {
this.$message.success('Delete Successfully');
this.getTableData({ page: this.paginationData.current_page });
})
.error(err => {
this.$message.error(err);
});
},
// Selection methods
handleSelectionChange(selection) {
this.selected = selection;
},
getRowKeys(row) {
return row.id;
},
// Multiple Delete
handleMultipleDelete() {
axios
.delete(
`/api/${this.resourceName}/delete-multiple?ids=${this.selected
.map(item => item.id)
.join(',')}`
)
.then(async () => {
this.$message.success('Records deleted successfully');
await this.getTableData({ page: this.paginationData.current_page });
if (this.tableData.length === 0) {
await this.getTableData({
page: this.paginationData.current_page,
});
}
this.$refs.table.clearSelection();
})
.catch();
},
// Import Events
handleImportSucces() {
this.$message.success('New Data Imported');
this.getTableData({});
},
handleImportError(err) {
this.$message.error('There were some errors. CHK console');
console.log(err);
},
},
};
</script>
<style lang="scss" scoped>
</style>
I guess this is a reactive issue. The value of dict and actually not affecting the feildData dict.
changes -
this.fieldData[field.name] = {};
replace this with
self.$set(self.fieldData,field.name,{});
and
this.fieldData[field.name][data.id] = data[field.name] // to
//Replace
self.$set(self.fieldData[field.name],data.id, data[field.name]);
I think this will fix the issue.Instead of using = use $set for value assignment.
Codepen - https://codepen.io/Pratik__007/pen/gObGOKx

Draggable table with bootstrap vue

I have been looking for a way to drag and drop rows on a Bootstrap Vue table.
I was able to find a working version here: Codepen
I have tried to implement this code to my own table:
Template:
<b-table v-sortable="sortableOptions" #click="(row) => $toast.open(`Clicked ${row.item.name}`)" :per-page="perPage" :current-page="currentPage" striped hover :items="blis" :fields="fields" :filter="filter" :sort-by.sync="sortBy" :sort-desc.sync="sortDesc" :sort-direction="sortDirection" #filtered="onFiltered">
<template slot="move" slot-scope="row">
<i class="fa fa-arrows-alt"></i>
</template>
<template slot="actions" slot-scope="row">
<b-btn :href="'/bli/'+row.item.id" variant="light" size="sm" #click.stop="details(cell.item,cell.index,$event.target)"><i class="fa fa-pencil"></i></b-btn>
<b-btn variant="light" size="sm" #click.stop="details(cell.item,cell.index,$event.target)"><i class="fa fa-trash"></i></b-btn>
</template>
<template slot="priority" slot-scope="row">
<input v-model="row.item.priority" #keyup.enter="row.item.focussed = false; updatePriority(row.item), $emit('update')" #blur="row.item.focussed = false" #focus="row.item.focussed = true" class="form-control" type="number" name="priority" >
</template>
</b-table>
Script:
import Buefy from 'buefy';
Vue.use(Buefy);
const createSortable = (el, options, vnode) => {
return Sortable.create(el, {
...options
});
};
const sortable = {
name: 'sortable',
bind(el, binding, vnode) {
const table = el.querySelector('table');
table._sortable = createSortable(table.querySelector('tbody'), binding.value, vnode);
}
};
export default {
name: 'ExampleComponent',
directives: { sortable },
data() {
let self = this;
return {
blis: [],
currentPage: 1,
perPage: 10,
pageOptions: [ 5, 10, 15 ],
totalRows: 0,
sortBy: null,
sortDesc: false,
sortDirection: 'asc',
sortableOptions: {
chosenClass: 'is-selected'
},
filter: null,
modalInfo: { title: 'Title', content: 'priority' },
fields: [
{
key: 'move',
sortable: true
},
///...rest of the fields
]
}
};
Now I have been getting this error: Error in directive sortable bind hook: "TypeError: Cannot read property 'querySelector' of null"
Why is it not able to find the <tbody> ?
Edit: https://jsfiddle.net/d7jqtkon/
In line const table = el.querySelector('table'); you are trying to get the table element. The var el is the table element. That is why it return null when you use querySelector
after assigning the correct table variable the error disappears
const table = el;
table._sortable = createSortable(table.querySelector("tbody"), binding.value, vnode);
Link to working fiddle
new Vue({
el: "#app",
directives: {
sortable: {
bind(el, binding, vnode) {
let self =el
Sortable.create(el.querySelector('tbody'),{
...binding.value,
vnode:vnode,
onEnd: (e) => {
let ids = el.querySelectorAll("span[id^=paper_]")
let order = []
for (let i = 0; i < ids.length; i++) {
let item = JSON.parse(ids[i].getAttribute('values'))
//extract items checkbox onChange v-model
let itemInThisData = vnode.context.items.filter(i => i.id==item.id)
order.push({
id:item.id,
paper: item.paper,
domain:item.domain,
platform: item.platform,
country:item.country,
sort_priority: item.sort_priority,
selectpaper:itemInThisData[0].selectpaper
})
}
binding.value = []
vnode.context.items = []
binding.value = order
vnode.context.items = order
console.table(vnode.context.items)
},
});
},
}
},
mounted() {
this.totalRows = this.items?this.items.length: 0
},
methods:{
onFiltered(filteredItems) {
// Trigger pagination to update the number of buttons/pages due to filtering
this.totalRows = filteredItems.length
this.currentPage = 1
},
log(){
console.table(this.items)
console.log(this)
},
},
data(){
return {
rankOption:'default',
totalRows: 1,
currentPage: 1,
filter: null,
filterOn:[],
sortBy:'paper',
sortDesc: false,
sortableOptions: {
chosenClass: 'is-selected'
},
perPage: this.results_per_page==='Todo' ? this.items.length : this.results_per_page?this.results_per_page:50,
pageOptions: [10, 50, 100, 500,'Todo'],
sortDirection: 'asc',
fields : [
{ key: 'paper', label: 'Soporte', sortable: true},
{ key: 'domain', label: 'Dominio', sortable: true},
{ key: 'platform', label: 'Medio', sortable: true},
{ key: 'country', label: 'País', sortable: true},
{ key: 'sort_priority', label: 'Rank', sortable: true},
{ key: 'selectpaper', label: 'Selección', sortable: true},
],
items : [
{
id:12,
paper: 'Expansion',
domain:'expansion.com',
platform: 'p',
country:'España',
sort_priority: '',
selectpaper:false
},
{
id:13,
paper: 'El economista',
domain:'eleconomista.es',
platform: 'p',
country:'España',
sort_priority: '',
selectpaper:false
},
{
id:14,
paper: 'El país',
domain:'elpais.es',
platform: 'p',
country:'España',
sort_priority: '',
selectpaper:false
}
]
}
}
})
<div id="app">
<template id="">
<b-table
:sort-by.sync="sortBy"
:sort-desc.sync="sortDesc"
v-sortable="items"
show-empty
small
stacked="md"
:items="items"
:fields="fields"
:current-page="currentPage"
:per-page="perPage"
:filter="filter"
:filterIncludedFields="filterOn"
:sort-direction="sortDirection"
#filtered="onFiltered"
>
<template v-slot:cell(selectpaper)="row">
<span :id="'paper_'+row.item.id" :values="JSON.stringify(row.item)"></span>
<b-form-group>
<input type="checkbox" #change="log" v-model="row.item.selectpaper" />
</b-form-group>
</template>
<template v-slot:cell(sort_priority)="row" v-if="rankOption==='foreach-row'">
<b-form-group>
<b-form-input type="number" #change="log"
size="sm" placeholder="Rank" v-model="row.item.sort_priority">
</b-form-input>
</b-form-group>
</template>
</b-table>
</template>
</div>
<script src="//unpkg.com/vue#latest/dist/vue.min.js"></script>
<script src="//unpkg.com/bootstrap-vue#latest/dist/bootstrap-vue.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/sortablejs#latest/Sortable.min.js"></script>

Objects gets empty in VueJS Component

I'm trying to build an application in VueJS where I'm having a component something like this:
<template>
<div>
<base-subheader title="Members" icon="la-home" heading="Member Details" description="Home"></base-subheader>
<div class="m-content">
<div class="row">
<div class="col-xl-6">
<nits-form-portlet
title="Add Member/User"
info="Fill the details below to add user, fields which are mandatory are label with * (star) mark."
headIcon="flaticon-multimedia"
headerLine
apiUrl="api/user"
backUrl="members__home"
action="create"
:formElements="form_elements"
>
</nits-form-portlet>
</div>
<div class="col-xl-6">
</div>
</div>
</div>
</div>
</template>
<script>
export default {
name: "member-add",
data() {
return {
form_elements: [
{
field_name: 'first_name',
config_elements: {
label: 'First Name *',
type: 'text',
inputStyle: 'pill',
placeholder: 'Enter first name of user',
formControl: true,
addonType: 'left-icon',
addon: {leftAddon: 'la-exclamation'},
},
value: '',
nitsFormType: 'nits-input'
},
{
field_name: 'last_name',
config_elements: {
label: 'Last Name',
type: 'text',
inputStyle: 'pill',
placeholder: 'Enter last name of user',
formControl: true,
addonType: 'left-icon',
addon: {leftAddon: 'la-exclamation'},
},
value: '',
nitsFormType: 'nits-input'
},
{
field_name: 'email',
config_elements: {
label: 'Email *',
type: 'email',
inputStyle: 'pill',
placeholder: 'Enter email of user',
formControl: true,
addonType: 'left',
addon: {leftAddon: '#'},
},
value: '',
nitsFormType: 'nits-input'
},
{
field_name: 'password',
config_elements: {
label: 'Password *',
type: 'password',
inputStyle: 'pill',
placeholder: 'Enter password',
formControl: true,
addonType: 'left-icon',
addon: {leftAddon: 'la-expeditedssl'},
},
value: '',
nitsFormType: 'nits-input'
},
{
field_name: 'confirm_password',
config_elements: {
label: 'Confirm Password *',
type: 'password',
inputStyle: 'pill',
placeholder: 'Confirm password should match',
formControl: true,
addonType: 'left-icon',
addon: {leftAddon: 'la-expeditedssl'},
},
value: '',
nitsFormType: 'nits-input'
},
{
field_name: 'role',
config_elements: {
label: 'Select Role *',
inputStyle: 'pill',
options: [
{option: 'Select one'},
{value: '1', option: 'Admin'},
{value: '2', option: 'Subscriber'},
{value: '3', option: 'Analyst'},
{value: '4', option: 'Guest'}
],
addonType: 'left-icon',
addon: {leftAddon: 'la-user-secret'},
},
value: '',
nitsFormType: 'nits-select'
},
{
field_name: 'profile_pic',
config_elements: {
label: 'Profile pic',
},
value: '',
nitsFormType: 'nits-file-input'
}
],
}
}
}
</script>
<style scoped>
</style>
Whenever I try to pass data to my component config_elements which holds an object of all attributes being passed to child component gets lost, if I move from other component to nits-form-portlet> it renders the child components appropriately, but in my Vue-debug tool it shows empty:
But the components are rendered properly:
And same happens to the props inside the component:
But in case of any event or refreshing the page it shows:
Since all the attributes are gone, if I try to configure my config_elements object data, I get the attributes but within a fraction of seconds it again goes to empty. But attributes gets passed and it displays the fields:
I am using render function to render these components so for nits-form-portlet component I have:
return createElement('div', { class: this.getClasses() }, [
createElement('div', { class: 'm-portlet__head' }, [
createElement('div', { class: 'm-portlet__head-caption' }, [element])
]),
createElement('form', { class: 'm-form m-form--fit m-form--label-align-right' }, [
createElement('div', { class: 'm-portlet__body' }, [
createElement('div', { class: 'form-group m-form__group m--margin-top-10' }, [infoElement]),
this.computedFormElements.map(a => {
if(this.error[a.field_name]) {
a.config_elements.error = this.error[a.field_name][0];
return createElement(a.nitsFormType, { attrs: a.config_elements, on: {
input: (event) => {
this.form[a.field_name] = event
}
}})
}
else
return createElement(a.nitsFormType, { attrs: a.config_elements, on: {
input: (event) => {
this.form[a.field_name] = event
}
}})
})
]),
footerElement
])
])
And I think factors affecting the template is this map statement:
this.computedFormElements.map(a => {
if(this.error[a.field_name]) {
a.config_elements.error = this.error[a.field_name][0];
return createElement(a.nitsFormType, { attrs: a.config_elements, on: {
input: (event) => {
this.form[a.field_name] = event
}
}})
}
else
return createElement(a.nitsFormType, { attrs: a.config_elements, on: {
input: (event) => {
this.form[a.field_name] = event
}
}})
})
but still I'm sharing my whole code of render function, Link to code # github hoping someone can help me with this strange situation.
It's a bit hard to tell for me without running the code, but I've noticed something that may be related.
I see that you're overwriting the formElements prop here:
a.config_elements.error = this.error[a.field_name][0];
Vue usually gives you a warning that you can't do that, but maybe because it's abstracted through the computed it's not doing so.
If you want to extend the data for use in the child component, then it's best to do copy of the object (preferably a deep copy using computed, which will allow you to have it dynamically updated)
because config_elements is an object, when you add error variable you are likely triggering an observer, that may be clearing the data in the parent.
Anyway, it's just a guess, but something you may want to resolve anyway.

Categories

Resources