Function call in v-slot, vue.js - vue.js

I'm trying to process some data in v-slot template, but I get the following error:
getNameFromCategory is not defined
What is wrong?
<template v-slot:items="{ row }" >
<TableView :rows="results" >
<td>{{ getNameFromCategory(row.category) }}</td>
</TableView>
</template>
<script>
export default {
components: {
TableView
},
metdods:{
getNameFromCategory
},
props: {
'results': {
type: Array,
required: true
},
}
</script>
and then in the TableVue.vue component file:
<template>
<template v-if="numRows > 0">
<tr
v-for="(row, idx) in rows"
:key="idx"
>
<slot name="items" :row="row"></slot>
</tr>
</template>
</template>
<script>
export default {
name: 'TableView',
props:{
rows: {
type: Array,
default() {
return []
},
required: true
},
}
}
</script>
It seems that the code passed through the slot template is executed in the component, but I also tried to define the 'getNameFromCategory' function there, it didn't work.

You wrote metdods instead of methods. Also the getNameFromCategory is empty i don't know if it is for the sake of the example.
Anyway it should more look like this :
<template v-slot:items="{ row }" >
<TableView :rows="results" >
<td>{{ getNameFromCategory(row.category) }}</td>
</TableView>
</template>
<script>
export default {
components: {
TableView
},
methods:{
getNameFromCategory(category) {
return category.name;
}
},
props: {
'results': {
type: Array,
required: true
},
}
</script>

Related

How to pass custom props to component in Vue from function?

I want to pass isReadonly boolean value from first component to second.
And it does not work.
Edited after cafertayyar answer.
Method isReadonly moved from methods to computed.
First component:
<template>
<PreliminaryInformationUsageCode :is-readonly="isReadonly" />
</template>
<script>
import PreliminaryInformationUsageCode from './form/PreliminaryInformationUsageCode.vue'
export default {
name: 'FormPage',
computed: {
form() {
return this.$store.getters['form/form']
},
isReadonly: function() {
//return true
return false
}
},
components: {
PreliminaryInformationUsageCode,
},
}
</script>
Second component:
<template>
<v-select
v-model="usageCodesSelected"
:items="usageCodes"
item-text="name"
item-value="code"
label="Label"
multiple
hint="Hint"
persistent-hint
v-bind:readonly="isReadonly"
>
<template v-slot:selection="{ item, index }">
<v-chip v-if="index === 0">
<span>{{ item.name }}</span>
</v-chip>
<span
v-if="index === 1"
class="grey--text text-caption"
>
(+{{ usageCodesSelected.length - 1 }} дополнительно)
</span>
</template>
</v-select>
</template>
<script>
export default {
name: 'PreliminaryInformationUsageCode',
props: {
isReadonly: {
Boolean
},
},
data: function() {
return {
usageCodesSelected: [
],
usageCodes: [
],
}
},
}
</script>
Use this:
<PreliminaryInformationUsageCode :is-readonly="isReadonly"/>
and instead of using isReadonly function, define a computed like:
computed: {
isReadonly() {
return this.form.status.seq != 10;
}
}

Can Vue.Draggable be used with Vuetify v-data-table and allow utilisation of table v-slot:item.<name>?

Vuetify v-data-table supports several types of slots: v-slot:body, v-slot:item and v-slot:item.<name>.
We have been using v-slot:item.<name> extensively, as these provides a flexible way to style and process content in individual columns AND allow the table headers to be programmatically changed.
I'd like to add draggability to my v-data-table rows and have got this working using Vue.Draggable.
However the draggable component requires use of the v-data-table v-slot:body i.e. taking control of the full body of the table and thereby losing the flexibility of v-slot:item.<name>.
Is there a way these two components can be used together and provide v-slot:item.<name> support?
I have created a DataTableRowHandler component which allows v-slot:item.<name> support.
This is placed inside the draggable component, inserts the table <tr> element and feeds off the same "headers" array to insert <td> elements and v-slot:item.<name> entries. If no v-slot:item.<name> is defined then the cell value is output, in the same way that v-data-table works.
Here is the example component usage:
<v-data-table
ref="myTable"
v-model="selected"
:headers="headers"
:items="desserts"
item-key="name"
class="elevation-1"
>
<template v-slot:body="props">
<draggable
:list="props.items"
tag="tbody"
:disabled="!allowDrag"
:move="onMoveCallback"
:clone="onCloneCallback"
#end="onDropCallback"
>
<data-table-row-handler
v-for="(item, index) in props.items"
:key="index"
:item="item"
:headers="headers"
:item-class="getClass(item)"
>
<template v-slot:item.lock="{ item }">
<v-icon #click="item.locked = item.locked ? false : true">{{
item.locked ? "mdi-pin-outline" : "mdi-pin-off-outline"
}}</v-icon>
</template>
<template v-slot:item.carbs="{ item }">
{{ item.carbs }}
<v-icon>{{
item.carbs > 80
? "mdi-speedometer"
: item.carbs > 45
? "mdi-speedometer-medium"
: "mdi-speedometer-slow"
}}</v-icon>
</template>
</data-table-row-handler>
</draggable>
</template>
</v-data-table>
Here is the DataTableRowHandler component code
<template>
<tr :class="getClass">
<td v-for="(header, index) in headers" :key="index">
<slot :item="item" :name="columnName(header)">
<div :style="getAlignment(header)">
{{ getNonSlotValue(item, header) }}
</div>
</slot>
</td>
</tr>
</template>
<script>
export default {
name: "DataTableRowHandler",
components: {},
props: {
itemClass: {
type: String,
default: "",
},
item: {
type: Object,
default: () => {
return {};
},
},
headers: {
type: Array,
default: () => {
return [];
},
},
},
data() {
return {};
},
computed: {
getClass() {
return this.itemClass;
}
},
methods: {
columnName(header) {
return `item.${header.value}`;
},
getAlignment(header) {
const align = header.align ? header.align : "right";
return `text-align: ${align}`;
},
getNonSlotValue(item, header) {
const val = item[header.value];
if (val) {
return val;
}
return "";
},
},
};
</script>
An example of it's use is in this codesandbox link
I checked the source code of VDataTable and found that the content in tbody is generated by genItems().
The following example is implemented with functional components and is fully compatible with v-slot:item*:
<v-data-table ref="table" ...>
<template #body="props">
<draggable
v-if="$refs.table"
tag="tbody"
:list="props.items"
>
<v-nodes :vnodes="$refs.table.genItems(props.items, props)" />
</draggable>
</template>
<template #item.name="{ item }">
...
</template>
</v-data-table>
Here is the definition of the VNodes component:
components: {
VNodes: {
functional: true,
render: (h, ctx) => ctx.props.vnodes,
}
}

dynamically call an object property in a v-for loop

so i ran into a problem again,
i want to make a table component where you can send a array to the component, and it will render a table for you
we set it up like this
<template>
<section class="container">
<Apptable :search="true" :loader="true" title="User data" :data="users"/>
</section>
</template>
<script>
import Apptable from "~/components/table.vue";
export default {
components: {
Apptable
},
data() {
return {
users: [
{
id: 1,
name: "Lars",
Adres: "hondenstraat 21",
phone: "06555965"
},
{
id: 1,
name: "John",
Adres: "verwelstraat 35",
phone: "06555965"
}
]
};
}
};
</script>
i send data to the component and loop it from there like this
<template>
<section class="container">
<h2 v-if="title">{{title}}</h2>
<input v-if="search" class="search" placeholder="Search">
<button v-if="loader" class="update" #click="dialog = true">Update</button>
<table class="table">
<thead>
<tr class="tableheader">
<th v-for="(item, index) in Object.keys(data[0])" :key="index">{{item}}</th>
</tr>
</thead>
<tbody>
<tr class="userdata" v-for="(item, index) in data" :key="index">
<td v-for="(name, index) in Object.keys(data[index])" :key="index">{{//TODO: I WANT TO SELECT THE ITEM.DYNAMIC PROPERTY}}</td>
</tr>
</tbody>
</table>
<loader v-if="loader" :trigger="dialog"/>
</section>
</template>
<script>
import loader from "~/components/loader.vue";
export default {
components: {
loader
},
data() {
return {
dialog: false
};
},
watch: {
dialog(val) {
if (!val) return;
setTimeout(() => (this.dialog = false), 1500);
}
},
props: {
data: {
type: Array,
required: true
},
title: {
type: String,
required: false,
default: false
},
loader: {
type: Boolean,
required: false,
default: false
},
search: {
required: false,
type: Boolean,
default: true
}
}
};
</script>
so if you look at the table. were i left the todo, if i put in the {{item}} in the todo place. i will get this in my column
enter image description here
but i want to select the key of the object dynamically. but if i put {{item.name}} in the todo place it will not select the key dynamically.
so the point is that i want to dynamically call a property from the object in the v-for so the columns will get the data in the cells.
You should use item[name] instead of item.name
<tbody>
<tr class="userdata" v-for="(item, index) in data" :key="index">
<td v-for="(name, nIndex) in Object.keys(data[index])" :key="nIndex">
{{ item[name] }}
</td>
</tr>
</tbody>

Vue: data from grandparent to grandchild (search component)

In my Vue app I have a view ('projects.vue') that gets some .json and which has a child component ('subheader.vue') which imports a search/filter mixin. I had this working but I wanted to split out the search elements from the subheader component to its own component, so subheader would hold only the headings and then import the search component. Despite adapting the props and bindings from the working version, my new three-component setup is throwing an error. Here's the setup:
the searchMixin.js:
export default {
computed: {
filteredProjects: function() {
const searchTerm = this.search.toLowerCase();
if (!searchTerm) {
return false;
}
return this.projects.filter((project) => {
return (project.client.toLowerCase().match(searchTerm)) ||
(project.contacts.filter((el) => {
return el.name.toLowerCase().match(searchTerm);
}).length > 0) ||
(project.projectReference.toLowerCase().match(searchTerm));
});
}
}
}
Projects.vue:
<template>
<div class="container" id="projects">
<!-- app-subheader is global component, imported & registered in main.js -->
<app-subheader v-bind:title="title" v-bind:subtitle="subtitle" />
[ snip irrelevant stuff ]
</div>
</template>
<script>
export default {
data () {
return {
title: "Projects",
subtitle: "",
projects: []
}
},
created: function() {
this.$http.get('https://xyz.firebaseio.com/projects.json')
.then(function(data){
return data.json();
})
.then(function(data){
var projectsArray = [];
for (let key in data) {
data[key].id = key;
this.projectID = key;
projectsArray.push(data[key]);
}
this.projects = projectsArray;
})
},
} // export default
</script>
subheader.vue:
<template>
<div class="subheader">
<div class="headings">
<h1 v-html="title"></h1>
<h2 v-html="subtitle"></h2>
<!-- I want to conditionally include <app-search /> according to an ID on an element in the grandparent (e.g., projects.vue) -->
<app-search v-bind:projects="projects" />
</div>
</div>
</template>
<script>
import Search from './search.vue';
export default {
components: {
'app-search': Search
},
props: [ "title", "subtitle", "projects" ],
data() {
return {
search: "",
projects: []
}
},
created: function() {
console.log("created; log projects", this.projects);
},
methods: {},
computed: {}
}
</script>
search.vue:
<template>
<div class="search-wrapper">
<div class="search">
<input type="text" v-model="search" placeholder="search by client, contact name, description, project, source" />
</div>
<div class="search-results-wrapper">
<h3>search-results:</h3>
<span class="results-count" v-if="filteredProjects.length == 0">
no results matching search "{{ search }}":
</span>
<span class="results-count" v-if="filteredProjects.length > 0">
{{ filteredProjects.length }} result<span v-if="filteredProjects.length > 1">s</span> matching search "{{ search }}":
</span>
<ul class="search-results" v-bind:class="{ open: filteredProjects.length > 0 }">
<li v-for="(project, ix) in filteredProjects" :key="ix">
{{ ix + 1 }}:
<router-link v-bind:to="'/project-detail/' + project.id">
{{ project.client }} ({{ project.projectReference }})
</router-link>
</li>
</ul>
</div>
</div><!-- END .search-wrapper -->
</template>
<script>
import searchMixin from '../mixins/searchMixin.js';
export default {
props: [ "projects" ],
data() {
return {
search: "",
}
},
created: function() {
},
mixins: [ searchMixin ],
}
When the search function is invoked, this error is thrown:
[Vue warn]: Error in render: "TypeError: Cannot read property 'filter' of undefined"
found in --->
<AppSearch> at src/components/search.vue
<AppSubheader> at src/components/subheader.vue
<Projects> at src/components/projects.vue
<App> at src/App.vue
... which seems to suggest the search mixin is not getting 'projects'.
Also, in subheader.vue, I get various errors whether I have 'projects' as a prop or 'projects: []' as a data key, and in once case or another I either get no results from the search function or an error, "Property or method "projects" is not defined on the instance but referenced during render".
Obviously I'm lacking clarity on the docs re; grandparent-parent-child data flow. Any help is greatly appreciated.
Whiskey T.

Vue 2 - change data value of all components

I have following setup:
Vue code:
Vue.component('ordering-filters', {
template: `
<a href="#"
:class="iconClass + faClass"
aria-hidden="true"
#click="orderCountries({orderBy, order})">
</a>`,
props: {
orderBy: {
type: String,
required: true
},
order: {
type: String,
required: true
},
iconClass: {
type: String,
default: "fa fa-lg text-muted"
},
faClass: {
type: String,
required: true
}
},
methods: {
orderCountries(params){
Event.$emit('ordering-filters', params);
}
},
data() {
return {
isActive: false
}
}
});
HTML code:
<tr>
<td class="col-md-6">Country Name
<div class="arrow-group">
<ordering-filters
order-by="name"
order="asc"
fa-class=" fa-sort-asc"
></ordering-filters>
<ordering-filters
order-by="name"
order="desc"
fa-class=" fa-sort-desc"
></ordering-filters>
</div>
</td>
<td class="countries-visible-filter col-md-3">
<visible-filters></visible-filters>
</td>
<td>Order
<div class="arrow-group">
<ordering-filters
order-by="order"
order="asc"
fa-class=" fa-sort-asc"
></ordering-filters>
<ordering-filters
order-by="order"
order="desc"
fa-class=" fa-sort-desc"
></ordering-filters>
</div>
</td>
<td>Actions</td>
</tr>
I want to change isActive to all the ordering-filters components when click event is fired, set it to false for all and then set it to true on the clicked element. Is this possible?
You must delegate the control of the active element to the parent somehow. Here is one of the many ways to do that, using v-model (which is just a shortcut for :value and #input), and a simple computed prop.
Vue.component('ordering-filters', {
template: `<a href="#" #click="orderCountries()">
{{ filterId }}
<template v-if="isActive"> I'm on. </template>
</a>`,
props: ['value', 'filterId'],
computed: {
isActive() {
return this.value === this.filterId;
}
},
methods: {
orderCountries() {
// Do some ordering stuff
this.$emit('input', this.filterId); // The active element is now this one
}
}
});
new Vue({
el: '#app',
data() {
return {
activeFilterId: null
};
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.4.1/vue.min.js"></script>
<div id="app">
<ordering-filters filter-id="one" v-model="activeFilterId"></ordering-filters> -
<ordering-filters filter-id="two" v-model="activeFilterId"></ordering-filters> -
<ordering-filters filter-id="three" v-model="activeFilterId"></ordering-filters>
</div>