Data not being passed to Vue component - javascript

I have a Vue component receiving an array of 'items' from its parent.
I've sorted them into categories, two 'items' in each category:
computed: {
// sort items into categories
glass: function() {
return this.items.filter(i => i.category === "glass").slice(0, 2);
},
ceramics:
// etc...
I need to place both items in categories.items to then pass them as props to another component:
data() {
return {
categories: [
{ name: "Glass", sort: "glass", items: {} },
{ name: "Ceramics", sort: "ceramics", items: {} },
{ name: "Brass", sort: "brass", items: {} },
{ name: "Books/Comics", sort: "books", items: {} },
{ name: "Collectibles", sort: "collectibles", items: {} },
{ name: "Pictures", sort: "pictures", items: {} },
{ name: "Other", sort: "other", items: {} }
]
};
},
When I use created or mounted nothing is passed through, when I use beforeDestroy or destroy and console.log the results it works fine, but, they're of no use when exiting the page.
The 'items' are from an Axios GET request, could this be why?
GET request from parent component:
methods: {
fetchItems() {
// items request
let uri = "http://localhost:8000/api/items";
this.axios.get(uri).then(response => {
// randomize response
for (let i = response.data.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1));
[response.data[i], response.data[j]] = [
response.data[j],
response.data[i]
];
}
this.items = response.data;
});
}
},
Passing props to child component:
<div
class="items-container"
v-for="category in categories"
:key="category.name"
>
<router-link :to="'category.link'" class="category-names-home-link">
<h2 class="category-names-home">{{ category.name }}</h2>
</router-link>
<router-link
:to="'category.link'"
class="home-view-all"
#mouseover.native="expand"
#mouseout.native="revert"
>View All...</router-link
>
<div class="items">
<!-- Pass to child component as props: -->
<SubItem :item="categories.items" />
<SubItem :item="categories.items" />
</div>
</div>

Don't bother adding the items to the categories, keep them separate
Instead of multiple computeds, use one computed object hash to store all the filtered sets:
computed: {
filtered() {
if (!this.items) return null;
const filtered = {};
this.items.forEach(item => {
if (filtered[item.category]) {
filtered[item.category].push(item);
} else {
filtered[item.category] = [item];
}
});
return filtered;
}
}
Result:
{
'Glass': [ ... ],
'Ceramic': [ ... ]
...
}
In the template:
<div>
<div v-for="category in categories" :key="category.name">
<div class="items" v-for="item in filtered[category.name]">
<SubItem :item="item" />
</div>
</div>
</div>
You can use a v-if in the parent to prevent displaying anything until the data is loaded:
<display v-if="items" :items="items"></display>
Here is a demo

Related

How to run a method using v-for in Vue.js?

I want to get the following output for the following data.
・3
・1
and sample data :
export const dummyData = [
{
id: "1",
name: "a",
sub: [
{
id: "1#1",
name: "b",
sub_sub: [
{ id: "1#1#1", name: "b-a" },
{ id: "1#1#2", name: "b-b" },
]
},
{
id: "1#2",
name: "c",
sub_sub: [
{ id: "1#2#1", name: "c-a" },
]
},
]
},
{
id: "2",
name: "d",
sub: [
{
id: "2#1",
name: "e",
sub_sub: [
{ id: "1#2#1", name: "e-a" },
]
}
]
},
]
I want to count how many elements of sub_sub are includes in object "a" and "d".
So, I made the following code.
<template>
<div>
<ul>
<li v-for="item in items" :key="item.i">{{rowSpanCalc(item.id)}}</li>
</ul>
</div>
</template>
<script lang="ts">
import { Component, Vue } from 'nuxt-property-decorator'
import { dummyData } from '~/store/dummy'
#Component({})
export default class extends Vue {
items: any = []
created() {
this.items = dummyData
}
rowSpanCalc(item: any) {
const count = item.sub.reduce(
(total: any, curr: any) => total + curr.sub_sub.length,
0
)
return count;
}
}
</script>
I ran my code and got an error in console like
  
  item.sub.reduce is not a function
Could anyone please advise me how to fix this errors?
Methods in the template are used as events handler not for rendering, try to use that method inside a computed property then use that property for render your items :
#Component({})
export default class extends Vue {
items: any = []
created() {
this.items = dummyData
}
get customItems(){
return this.items.map(item=>({...item,count:this.rowSpanCalc(item.id)}))
}
rowSpanCalc(item: any) {
const count = item.sub.reduce(
(total: any, curr: any) => total + curr.sub_sub.length,
0
)
return count;
}
}
template :
...
<li v-for="item in customItems" :key="item.id">{{item.count}}</li>
...

How to use conditional rendering with Vue.js slots?

How can I use the result of a v-if to render two different components in Vue? If it matters, I'm also trying to do this while rendering a TreeView using v-for. Here's what I tried so far.
TreeView.vue:
<template>
<span>
<ul v-show="isOpen" v-if="isFolder">
<p
v-for="(child, index) in item.children"
v-bind:item="child"
v-bind:key="index"
<span v-if="!child.isFolder">
<slot v-bind:individualBookmark="child"></slot>
</span>
<span v-else>
<div v-bind:class="{bold: isFolder}" v-on:click="toggle" v-on:dblclick="makeFolder" v-on:contextmenu="rightClick">
{{ child.name }}
<span v-if="isFolder">[{{ isOpen ? '-' : '+' }}]</span>
</div>
</span>
</p>
</ul>
</span>
</template>
<script>
export default {
name: "TreeView",
props: {
item: Object
},
components: {
},
data: function() {
return {
isOpen: false
}
},
computed: {
isFolder: function() {
return this.item.children.length > 0;
//return this.item.children && this.item.children.length;
}
},
methods: {
toggle: function() {
if (this.isFolder) {
this.isOpen = !this.isOpen;
}
},
makeFolder: function() {
if (!this.isFolder) {
this.$emit("make-folder", this.item);
this.isOpen = true;
}
},
rightClick: function() {
alert("Right click action")
}
}
}
</script>
App.vue (the Bookmark tag is a custom component I built that does not have the isFolder computed property):
<template>
<div id="app">
<TreeView v-slot:default="slotProps"
v-bind:item="treeData"
v-on:make-folder="makeFolder"
v-on:add-item="addItem">
<Bookmark v-bind="slotProps.individualBookmark"></Bookmark>
</TreeView>
</div>
</template>
<script>
import Bookmark from './components/Bookmark.vue'
import TreeView from './components/TreeView.vue'
import Vue from 'vue'
export default {
name: 'App',
components: {
Bookmark,
TreeView
},
data: function() {
return {
treeData : {
name: "My Tree",
children: [
{ name: "hello" },
{ name: "wat" },
{
name: "child folder 1",
children: [
{
name: "child folder",
children: [{ name: "hello" }, { name: "wat" }]
},
{ name: "hello" },
{ name: "wat" },
{
name: "child folder",
children: [{ name: "hello" }, { name: "wat" }]
}
]
}
]
}
}
},
methods: {
makeFolder: function(item) {
Vue.set(item, "children", []);
this.addItem(item);
},
addItem: function(item) {
item.children.push({
name: "new stuff"
});
}
}
}
</script>
When I run this, for some reason, the v-if="!child.isFolder" in TreeView.vue always evaluates to true so it renders the Bookmark component. Even in the case when the v-if should be evaluating to false like when given the "child folder 1" child object of treeData, it still evaluates to true. I have a feeling the problem is to do with how I'm using <slot></slot> in TreeView.vue but I'm not sure. Why is the v-if always evaluating to true and never false?
The problem may also have to do with how I wrote the isFolder property.
Add a computed property called computedChildren inside the Treeview component which maps the item prop by adding isFolder property :
computed: {
isFolder: function() {
return this.item.children.length > 0;
//return this.item.children && this.item.children.length;
},
computedChildren(){
return this.item.children.map(child=>{
child.isFolder=child.children?child.children.length>0:false //this add the property isFolder
return child
})
}
},
Then loop through it as follows :
<p
v-for="(child, index) in computedChildren"
...
For replacing the slot you could use template element :
<TreeView
v-bind:item="treeData"
v-on:make-folder="makeFolder"
v-on:add-item="addItem">
<template v-slot:default="slotProps">
<Bookmark v-bind="slotProps.individualBookmark"></Bookmark>
</template>
</TreeView>

VueJS How to filter array data when using dropdown select

In my vue-app I have an array of job-postings, which have different states, such as "active", "rejected", "draft", "not_active" etc. Now I have a TabMenu: All Jobs, Drafts and To Be Approved. Each of those MenuTabs, have their own Dropdown menu, where you are supposed to filter the jobpostings. I've realized that this feature is more complex than expected, or maybe I have spend too much time with the issues, but for some reason, I cannot manage, to show "all" for the individual MenuTab. For example, when I click on the "To Be Approved" MenuTab, I want to see all the jobpostings, with the status "Not approved" and "Rejected" (See data below in the code).
So my question is, how to solve this properly? Does the job-posting data object need to have a category too?
Any help is most welcome!
So, here is my component:
<template>
<div>
<div class="tabs">
<ul>
<li v-for="(tab, index) in menuTabs” :key="tab.id" :class="{ 'active': activeTab === index }"
#click="toggleList(tab, index)” >
<span>{{tab.label}}</span>
</li>
</ul>
</div>
<div class="dropdown has-prepend col-8" :class="{ active: isOpen }">
<div :class="{ active: isOpen }" class="dropdown-select" #click="toggle">
{{ selectedOption }}
<i class="icon-chevron_down" />
</div>
<div class="dropdown-options" v-show="isOpen">
<div class="option" v-for="tab in dropDownTabs" #click="select(tab)" :key="tab.id">
<span>{{ tab.status }}</span>
</div>
</div>
</div>
<div class="block">
<DataTable :data="filteredData" :columns="tableColumns" :filter="search" />
</div>
</div>
</template>
import DataTable from '../../snippets/DataTable';
export default {
components: { DataTable },
data() {
return {
isOpen: false,
search: "",
tableData: [
{
id: 1,
title: "Salesperson",
publish_date: "2019-07-10",
status: "active",
applicants: 23,
expiration_date: "2020-02-21"
},
{
id: 2,
title: "Developer",
publish_date: "2019-11-12",
status: "not_active",
applicants: 111,
expiration_date: "2020-02-21"
},
{
id: 3,
title: "Freelanceer",
publish_date: "2019-06-10",
status: "need_approval",
applicants: 222,
expiration_date: "2020-01-10"
},
{
id: 4,
title: "Construction worker",
publish_date: "2019-12-06",
status: "active",
applicants: 76,
expiration_date: "2020-03-15"
},
{
id: 5,
title: "IT support”
publish_date: "2019-11-20",
status: "draft",
applicants: 103,
expiration_date: "2020-04-31"
},
],
menuTabs: [
{
label: "All jobs",
options: [
{
status: "all",
},
{
status: "active",
},
{
status: "not_active"
}
]
},
{
label: "Drafts",
options: [
{
status: "all"
},
{
status: "draft"
}
]
},
{
label: "To Be Approved",
options: [
{
status: "all",
},
{
status: "need_approval",
},
{
status: "rejected"
}
]
},
],
dropDownTabs: [],
selectedOption: "",
selectedTabCategory: "",
category: "",
activeTab: "",
tableColumns: [
"id",
"title",
"publish_date",
"status",
"applicants",
"expiration_date"
]
}
},
computed: {
filteredData() {
let status = this.selectedOption;
let category = this.category;
let filtered = this.tableData.filter(data => {
if (status == "all") {
return data;
}
return data.status === status;
});
return filtered;
}
},
methods: {
toggleList(tab, index) {
this.category = tab.options[0].category;
this.selectedTabCategory = tab;
let currentTabOptions = this.selectedTabCategory.options;
this.clearDropDown();
this.selectedOption = currentTabOptions[0].status;
currentTabOptions.forEach(option => {
this.dropDownTabs.push(option);
});
this.activeTab = index;
},
toggle() {
this.isOpen = !this.isOpen;
},
select(tab) {
this.selectedOption = tab.status;
let category = tab.category;
let filtered = this.tableData.filter(data => {
return data.status === this.selectedOption;
});
this.isOpen = false;
return filtered;
},
clearDropDown() {
this.dropDownTabs = [];
}
},
created() {},
mounted() {
this.selectedOption = this.menuTabs[0].options[0].status;
this.selectedTabCategory = this.menuTabs[0].label;
this.category = this.menuTabs[0].options[0].category;
let defaultOptions = this.menuTabs[0].options;
defaultOptions.forEach(option => {
this.dropDownTabs.push(option);
});
this.activeTab = 0;
}
};
I am not sure if it will help you at all. However I will try anyway.
You should store the selected tab when you click on it. Then filter the this.tableData based on the selected tab options. Also you will need map the tab option options to array of strings, so you can check if the posting status is in there.
methods: {
toggleList (tab, index) {
this.selectedTabObject = tab
// rest of your code...
}
},
computed: {
filteredData () {
return this.tableData.filter(data => {
const states = this.selectedTabObject.options.map(opt => opt.status)
return states.includes(data.status)
})
}
}
Also I have created simple fiddle to mimic your problem.
https://jsfiddle.net/3hqnp7u2/7/

vue v-for object props

I am trying to pass an array object to a component, but am not getting anything
create.vue
<el-row >
<component1 v-for="product in products" :value="product" :key="product.id"></component1>
</el-row>
//script section
data() {
return {
products: [] //there have (id = 1, name = first), (id = 2, name = second)
}
}
component1.vue
<el-row>
<div>
{{ product.name }}
</div>
</el-row>
export default {
props: ['value'],
watch: {
value: {
hander: function (val) {
console.log(val);
this.product = {
id: val.id,
name: val.name
}
},
deep: true
}
},
data() {
return {
product: {
id: null,
name: null
}
}
},
but watch not worked ( {{product.name}} its null), why? or how fixed this?
You're trying to watch a property that doesn't change, in your example i don't figure out the need of using watch property, but you could achieve that by assigning value to your data property called product in the mounted life cycle hook as follows :
<el-row>
<div>
{{ product.name }}
</div>
</el-row>
export default {
props: ['value'],
watch: {
value: {
handler: function (val) {
console.log(val);
this.product = {
id: val.id,
name: val.name
}
},
deep: true
}
},
data() {
return {
product: {
id: null,
name: null
}
}
},
mounted(){
this.product= {
id: this.value.id,
name: this.value.name
}
}

Multiple select Vue.js and computed property

I'm using Vue.js 2.0 and the Element UI library.
I want to use a multiple select to attribute some roles to my users.
The list of all roles available is received and assigned to availableRoles. Since it is an array of object and the v-model accepts only an array with value, I need to extract the id of the roles trough the computed property computedRoles.
The current roles of my user are received and assigned to userRoles: [{'id':1, 'name':'Admin'}, {'id':3, 'name':'User'}].
computedRoles is then equals to [1,3]
The preselection of the select is fine but I can't change anything (add or remove option from the select)
What is wrong and how to fix it?
http://jsfiddle.net/3ra1jscx/3/
<div id="app">
<template>
<el-select v-model="computedRoles" multiple placeholder="Select">
<el-option v-for="item in availableRoles" :label="item.name" :value="item.id">
</el-option>
</el-select>
</template>
</div>
var Main = {
data() {
return {
availableRoles: [{
id: 1,
name: 'Admin'
}, {
id: 2,
name: 'Power User'
}, {
id: 3,
name: 'User'
}],
userRoles: [{'id':1, 'name':'Admin'}, {'id':3, 'name':'User'}]
}
},
computed : {
computedRoles () {
return this.userRoles.map(role => role.id)
}
}
}
I agree mostly with #wostex answer, but he doesn't give you the userRoles property back. Essentially you should swap computedRoles and userRoles. userRoles becomes a computed property and computedRoles is a data property. In my update, I changed the name of computedRoles to selectedRoles.
var Main = {
data() {
return {
availableRoles: [{
id: 1,
name: 'Admin'
}, {
id: 2,
name: 'Power User'
}, {
id: 3,
name: 'User'
}],
selectedRoles:[1,2]
}
},
computed : {
userRoles(){
return this.availableRoles.reduce((selected, role) => {
if (this.selectedRoles.includes(role.id))
selected.push(role);
return selected;
}, [])
}
}
}
var Ctor = Vue.extend(Main)
new Ctor().$mount('#app')
And here is the fiddle.
Check the solution: jsfiddle
The caveat here is that computed properties are getters mainly. You can define setter for computed property, but my approach is more vue-like in my opinion.
In short, instead of v-model on computed set v-model for data property.
Full code:
<script src="//unpkg.com/vue/dist/vue.js"></script>
<script src="//unpkg.com/element-ui/lib/index.js"></script>
<div id="app">
<template>
<el-select v-model="ids" multiple placeholder="Select" #change="logit()">
<el-option v-for="item in availableRoles" :label="item.name" :value="item.id">
</el-option>
</el-select>
</template>
</div>
var Main = {
data() {
return {
availableRoles: [{
id: 1,
name: 'Admin'
}, {
id: 2,
name: 'Power User'
}, {
id: 3,
name: 'User'
}],
userRoles: [{'id':1, 'name':'Admin'}, {'id':3, 'name':'User'}],
ids: []
}
},
mounted() {
this.ids = this.userRoles.map(role => role.id);
},
methods: {
logit: function() {
console.log(this.ids);
}
}
}
var Ctor = Vue.extend(Main)
new Ctor().$mount('#app')

Categories

Resources