Going back to previous object in array with back button in v-for - vue.js

I have a component with a v-for div. each item has a click function access their respective children object. I need to have a back button that would refresh the v-for div but using the ParentId of the current item I'm in.
Scan view:
<template>
<div p-0 m-0>
<div v-show="!currentItem" class="scanBreadcrumbs">
<h2>Show location</h2>
</div>
<div v-for="item in items" :key="item.id" :item="item">
<SubScan
v-show="currentItem && currentItem.id === item.id"
:item="item"
></SubScan>
<p
class="locationBox"
#click="swapComponent(item)"
v-show="path.length === 0"
>
{{ item.title }}
</p>
</div>
</div>
</template>
<script>
import { mapGetters } from "vuex";
import { SubScan } from "#/components/scan";
export default {
name: "Scan",
components: {
SubScan
},
computed: {
...mapGetters(["getResourceHierarchy", "getIsDarkMode", "getFontSize"])
},
methods: {
swapComponent(item) {
this.path.push(item.title);
this.currentItem = item;
}
},
data: () => ({
currentItem: null,
path: [],
items: [
{
parentId: null,
id: 11,
title: "Location 1",
items: [
{
parentId: 11,
id: 4324,
title: "Row 1",
items: [
{
parentId: 4324,
id: 4355,
title: "Row 1.1",
items: [
{
parentId: 4355,
id: 64645,
title: "Row 1.2",
items: [
{
parentId: 64645,
id: 7576657,
title: "Row 1.3",
items: [
{
parentId: 7576657
id: 8686,
title: "Row 1.4",
items: [
{
parentId: 8686,
id: 234324,
title: "QR Code"
}
]
}
]
}
]
}
]
}
]
}
]
}
]
})
};
</script>
SubScan component where the back button is:
<template>
<div>
<div class="scanBreadcrumbs">
<h2 v-show="path">{{ path.join(" / ") }}</h2>
</div>
<div>
<div class="showList" v-for="item in itemChildren" :key="item.id">
<p class="locationBox" #click="swapComponent(item)">
{{ item.title }}
</p>
<div class="backButton">
<v-icon #click="swapPrevious(item)" class="arrow"
>fa-arrow-left</v-icon
>
</div>
</div>
</div>
</div>
</template>
<script>
export default {
name: "SubScan",
props: {
item: {
type: Object,
required: true
}
},
data: () => ({
currentItem: null,
secondaryPath: [],
parentPath: []
}),
methods: {
swapComponent(item) {
console.log(item.parentId);
this.path.push(item.title);
this.parentPath.push(this.currentItem);
this.currentItem = item;
},
swapPrevious(item) {
console.log(item);
this.path.pop(this.currentItem.title);
this.currentItem = item.id;
}
},
computed: {
items(currentItem) {
return this.currentItem ? this.item.items : this.item.items;
},
itemChildren(currentItem) {
return this.currentItem ? this.currentItem.items : this.item.items;
},
path() {
return this.secondaryPath.concat(this.item.title);
}
}
};
</script>
I can only go back to the children of the object I clicked on in Scan view.
Thank you for your time.

I managed to fix my problem by assigning parent objects to each children. Then I
moved everything to Scan.vue for simplicity. This is my first project using Vue
so things might not be optimal. Scan.vue
<template>
<div p-0 m-0>
<div class="scanBreadcrumbs">
<h2 v-show="path">{{ path.join("/") }}</h2>
<h2 v-if="path.length === 0">Show location</h2>
</div>
<div>
<div v-for="item in items">
<p class="locationBox" #click="swapComponent(item)">
{{ item.title }}
</p>
</div>
<div v-if="path.length > 0">
<div class="backButton">
<v-icon #click="swapPrevious()" class="arrow">fa-arrow-left</v-icon>
</div>
</div>
</div>
</div>
</template>
<script>
import { mapGetters } from "vuex";
export default {
name: "Scan",
computed: {
...mapGetters(["getResourceHierarchy", "getIsDarkMode", "getFontSize"])
},
methods: {
swapComponent(item) {
this.path.push(item.title);
this.currentItem = item;
this.items = this.currentItem.items;
},
assignParent(children, parent) {
children.forEach(item => {
item.Parent = parent;
var parentTitle = "";
if (parent) parentTitle = parent.title;
if (item.items) {
this.assignParent(item.items, item);
}
});
},
swapPrevious() {
if (this.currentItem.parentId === null) {
this.items = this.initialItems;
this.path.pop(this.currentItem.title);
} else {
this.currentItem = this.currentItem.Parent;
this.items = this.currentItem.items;
this.path.pop(this.currentItem.title);
}
}
},
mounted: function() {
this.assignParent(this.items, null);
this.initialItems = this.items;
},
data: () => ({
currentItem: null,
path: [],
initialItems: [],
items: [
{
parentId: null,
id: 11,
title: "Location 1",
items: [
{
parentId: 11,
id: 4324,
title: "Row 1",
items: [
{
parentId: 4324,
id: 4355,
title: "Row 1.1",
items: [
{
parentId: 4355,
id: 64646,
title: "Row 1.2",
items: [
{
parentId: 64646,
id: 7576657,
title: "Row 1.3",
items: [
{
parentId: 7576657,
id: 8686,
title: "Row 1.4",
items: [
{
parentId: 8686,
id: 12313,
title: "Row 1.5",
items: [
{
parentId: 12313,
id: 234324,
title: "QR Code"
}
]
}
]
}
]
}
]
}
]
}
]
}
]
}
]
})
};
</script>
<style lang="scss" scoped></style>

Related

VueJS - Pull results from a separate component

I have found an excellent example for creating URL filters. I would like the results to be in a separate component though. In this example, all the code is in one file. Can someone help me to move the results to a separate component and also ensure the URL filters work when the different selections are made?
<template>
<div id="app">
<div id="app">
<h2>Items</h2>
<p>
<input type="search" placeholder="Filter by name" v-model="filter" />
<input
type="checkbox"
value="person"
id="personType"
v-model="typeFilter"
/>
<label for="personType">Only People</label>
<input type="checkbox" value="cat" id="catType" v-model="typeFilter" />
<label for="catType">Only Cats</label>
<input type="checkbox" value="dog" id="dogType" v-model="typeFilter" />
<label for="dogType">Only Dogs</label>
</p>
<ul>
<li v-for="item in items" :key="item">
{{ item.name }} ({{ item.type }})
</li>
</ul>
</div>
</div>
<!-- <Result v-for="item in items" :key="item" /> -->
</template>
<script>
//import Result from "#/components/Result.vue";
export default {
components: {
//Result,
},
data: function () {
return {
// allItems: ITEMS,
filter: "",
typeFilter: [],
ITEMS: [
{ name: "Ray", type: "person" },
{ name: "Lindy", type: "person" },
{ name: "Jacob", type: "person" },
{ name: "Lynn", type: "person" },
{ name: "Noah", type: "person" },
{ name: "Jane", type: "person" },
{ name: "Maisie", type: "person" },
{ name: "Carol", type: "person" },
{ name: "Ashton", type: "person" },
{ name: "Weston", type: "person" },
{ name: "Sammy", type: "cat" },
{ name: "Aleese", type: "cat" },
{ name: "Luna", type: "cat" },
{ name: "Pig", type: "cat" },
{ name: "Cayenne", type: "dog" },
],
};
},
created() {
let qp = new URLSearchParams(window.location.search);
let f = qp.get("filter");
if (f) this.filter = qp.get("filter");
let tf = qp.get("typeFilter");
if (tf) this.typeFilter = tf.split(",");
},
computed: {
items() {
this.updateURL();
return this.ITEMS.filter((a) => {
if (
this.filter !== "" &&
a.name.toLowerCase().indexOf(this.filter.toLowerCase()) === -1
)
return false;
if (this.typeFilter.length && !this.typeFilter.includes(a.type))
return false;
return true;
});
},
},
methods: {
updateURL() {
let qp = new URLSearchParams();
if (this.filter !== "") qp.set("filter", this.filter);
if (this.typeFilter.length) qp.set("typeFilter", this.typeFilter);
history.replaceState(null, null, "?" + qp.toString());
},
},
};
</script>
You need to read about props in component (vuejs.org/v2/guide/components-props.html).
I wrote you an example but not tested, hope it will be helpful.
<template>
<div id="app">
<h2>Items</h2>
<p>
<input type="search" placeholder="Filter by name" v-model="filter" />
<input
type="checkbox"
value="person"
id="personType"
v-model="typeFilter"
/>
<label for="personType">Only People</label>
<input type="checkbox" value="cat" id="catType" v-model="typeFilter" />
<label for="catType">Only Cats</label>
<input type="checkbox" value="dog" id="dogType" v-model="typeFilter" />
<label for="dogType">Only Dogs</label>
</p>
<ul>
<li v-for="item in items" :key="item">
{{ item.name }} ({{ item.type }})
</li>
</ul>
<Result v-bind:items="filteredItems" />
</div>
</template>
<script>
import Result from "#/components/Result.vue";
export default {
components: {
Result,
},
data: function () {
return {
filter: "",
typeFilter: [],
items: [
{ name: "Ray", type: "person" },
{ name: "Lindy", type: "person" },
{ name: "Jacob", type: "person" },
{ name: "Lynn", type: "person" },
{ name: "Noah", type: "person" },
{ name: "Jane", type: "person" },
{ name: "Maisie", type: "person" },
{ name: "Carol", type: "person" },
{ name: "Ashton", type: "person" },
{ name: "Weston", type: "person" },
{ name: "Sammy", type: "cat" },
{ name: "Aleese", type: "cat" },
{ name: "Luna", type: "cat" },
{ name: "Pig", type: "cat" },
{ name: "Cayenne", type: "dog" },
],
};
},
created() {
let qp = new URLSearchParams(window.location.search);
let f = qp.get("filter");
if (f) {
this.filter = qp.get("filter");
}
let tf = qp.get("typeFilter");
if (tf) {
this.typeFilter = tf.split(",");
}
},
computed: {
filteredItems() {
this.updateURL();
return this.items.filter((item) => item.name.toLowerCase().includes(this.filter.toLowerCase()));
},
},
methods: {
updateURL() {
let qp = new URLSearchParams();
if (this.filter !== "") {
qp.set("filter", this.filter);
}
if (this.typeFilter.length) {
qp.set("typeFilter", this.typeFilter);
}
history.replaceState(null, null, "?" + qp.toString());
},
},
};
</script>
<template>
<div class="result">
<ul>
<li v-for="item in items" :key="item">
{{ item.name }} ({{ item.type }})
</li>
</ul>
</div>
</template>
<script>
export default {
props: ['items'],
};
</script>

Search bar filter vue 3

I just want to add a search bar filter to my vue 3 project but I don't know why is not working as good as I wanted.
Here is my code:
App.vue
<template>
<div id="app">
<div class="header">
<img class="logo" alt="UOC logo" src="./assets/uoc-logo.png" />
<div class="app-name">Recipe book</div>
</div>
<search-bar #search="setSearchTerm" />
<recipe-list :recipeList="filteredData" #delete-recipe="deleteRecipe" />
<recipe-form
v-if="showModal"
#add-recipe="addRecipe"
#close-modal="showModal = false"
/>
</div>
</template>
<script>
import RecipeList from "./components/RecipeList.vue";
import RecipeForm from "./components/RecipeForm.vue";
import SearchBar from "./components/SearchBar.vue";
import { defineComponent } from "vue";
export default defineComponent({
name: "App",
components: {
RecipeList: RecipeList,
RecipeForm,
SearchBar,
},
data: () => ({
recipeList: [
{
id: 1,
servings: 4,
time: "30m",
difficulty: "Easy",
title: "Spaghetti",
ingredients: ["noodles", "tomato sauce", "cheese"],
directions: ["boil noodles", "cook noodles", "eat noodles"],
imageUrl:
"https://imagesvc.meredithcorp.io/v3/mm/image?q=60&c=sc&poi=face&w=2000&h=1000&url=https%3A%2F%2Fstatic.onecms.io%2Fwp-content%2Fuploads%2Fsites%2F21%2F2018%2F02%2F14%2Frecetas-4115-spaghetti-boloesa-facil-2000.jpg",
},
{
id: 2,
servings: 2,
time: "15m",
difficulty: "Medium",
title: "Pizza",
ingredients: ["dough", "tomato sauce", "cheese"],
directions: ["boil dough", "cook dough", "eat pizza"],
imageUrl:
"https://www.saborusa.com/wp-content/uploads/2019/10/Animate-a-disfrutar-una-deliciosa-pizza-de-salchicha-Foto-destacada.png",
featured: true,
},
{
id: 3,
servings: 6,
time: "1h",
difficulty: "Hard",
title: "Salad",
ingredients: ["lettuce", "tomato", "cheese"],
directions: ["cut lettuce", "cut tomato", "cut cheese"],
imageUrl:
"https://www.unileverfoodsolutions.es/dam/global-ufs/mcos/SPAIN/calcmenu/recipes/ES-recipes/In-Development/american-bbq-beef-salad/main-header.jpg",
},
],
showModal: false,
recipesData: RecipeList,
filteredData: RecipeList,
}),
methods: {
deleteRecipe(id) {
this.recipeList.splice(id, 1);
},
addRecipe(recipe) {
this.recipeList.push(recipe);
},
toggleForm() {
if (this.showModal === false) {
this.showModal = true;
}
},
setSearchTerm(value) {
console.log(value);
if (value && value.length > 0) {
this.filteredData = this.recipesData.filter((i) => {
const val = value.toLowerCase();
const title = i.title && i.title.toLowerCase();
if (val && title.indexOf(val) !== -1) {
return true;
}
return false;
});
} else {
this.filteredData = this.recipesData;
}
},
},
});
</script>
SearchBar.vue
<template>
<div class="search">
<input
type="text"
placeholder="Search for a recipe"
id="search"
#change="search"
v-model="searchText"
/>
<button #click="clearSearch">Clear search</button>
<button #click="showForm">Add a new recipe</button>
</div>
</template>
<script>
import { defineComponent } from "vue";
export default defineComponent({
name: "SearchBar",
data() {
return { searchText: "" };
},
methods: {
showForm() {
console.log("show");
this.$emit("show-form");
},
clearSearch() {
this.clearInput = "";
},
search() {
this.$emit("search", this.searchText);
},
},
});
</script>
The component of search bar is working but when I acces to my website the array object is broken and I can not see any recipe. I just see recipes If I find it.
Thanks for your help

Hey! I want to implement some anchor links in vue with no library used

Here is my code below. I know there is some specific library like vue-scrollto but I want to resolve this task with no library.
I want to add isViewed true only in that certain link. But the problem is that when I clicked on links each object of that link has isViewed true instead of only one.I want it changes dynamically.
I would appreciate any help.
<template>
<div class="anchors">
<ul class="list">
<li class="item" v-for="(link, index) in anchorLinks" :key="link.id">
<a #click="goToSection(link, link.sectionId, link.id, index)" class="anchor__item-link"></a>
</li>
</ul>
</div>
</template>
export default {
name: "AnchorLinks",
data(){
return{
anchorLinks: [
{
id: 1,
sectionId: "work",
},
{
id: 2,
sectionId: "service",
},
{
id: 3,
sectionId: "partner",
},
{
id: 4,
sectionId: "partner",
},
{
id: 5,
sectionId: "partner",
},
{
id: 6,
sectionId: "partner",
},
{
id: 6,
sectionId: "partner",
}
]
}
},
methods: {
goToSection(link, sectionId, id, index){
const element = document.getElementById(sectionId);
if (element) {
link.isViewed = true;
window.scrollTo({
top: element.offsetTop,
behavior: 'smooth'
});
console.log(id)
console.log(index);
console.log(this.anchorLinks)
}
}
}
}
You should ensure that each anchorLink initially has an isViewed property,
The documentation states:
Vue cannot detect property addition or deletion
<template>
<div class="anchors">
<ul class="list">
<li class="item" v-for="(link, index) in anchorLinks" :key="link.id">
<a
#click="goToSection(link, link.sectionId, link.id, index)"
class="anchor__item-link"
></a>
</li>
</ul>
</div>
</template>
export default {
name: 'AnchorLinks',
data() {
return {
anchorLinks: [
{
id: 1,
sectionId: 'work',
isViewed: false,
},
{
id: 2,
sectionId: 'service',
isViewed: false,
},
{
id: 3,
sectionId: 'partner',
isViewed: false,
},
{
id: 4,
sectionId: 'partner',
isViewed: false,
},
{
id: 5,
sectionId: 'partner',
isViewed: false,
},
{
id: 6,
sectionId: 'partner',
isViewed: false,
},
{
id: 6,
sectionId: 'partner',
isViewed: false,
},
],
};
},
methods: {
goToSection(link, sectionId, id, index) {
const element = document.getElementById(sectionId);
if (element) {
link.isViewed = true;
window.scrollTo({
top: element.offsetTop,
behavior: 'smooth',
});
console.log(id);
console.log(index);
console.log(this.anchorLinks);
}
},
},
};
Without reinventing the wheel here, you basically need to remove the previous link.isViewed property each time you click a link.
One straightforward way to do this is to store another variable containing the active link, that you can update each time you click a new link. See simple example below (ignoring excess / cruft).
As an aside, I would consider not updating the isViewed variable at all and just storing the active link (similar to below) for reference wherever you need it.
<ul>
<li v-for="link in anchorLinks">
<a #click="goToSection(link)"></a>
</li>
</ul>
export default {
name: "AnchorLinks",
data() {
return {
activeLink: null,
// you're using an array here, lean on the index
anchorLinks: [
{ id: "work" },
{ id: "service" },
{ id: "partner" },
],
};
},
methods: {
goToSection(link) {
// remove the previous `isViewed` property
if (this.activeLink) {
delete this.activeLink.isViewed;
}
// update the active link
this.activeLink = link;
const el = document.getElementById(link.id);
if (el) {
// now this will be the only link with `isViewed = true`
link.isViewed = true;
window.scrollTo({
top: el.offsetTop,
behavior: 'smooth'
});
}
}
}
}
From what I can tell and from what you have given, I think you need to set the ids of each anchor link.
<a
:id="link.sectionId"
#click="goToSection(link, link.sectionId, link.id, index)"
class="anchor__item-link"
></a>

renderd items coming through event bus -Vue js

<template>
<div>
<h1>This is a data page</h1>
<p v-for="data in dummyData" :key="data.id">{{ data.title }}</p>
</div>
</template>
<script>
import { eventBus } from '../main'
export default {
name: "Data",
data() {
return {
dummyData: [
{
id: 1,
title: "Dummy Data One",
completed: false
},
{
id: 2,
title: "Dummy Data Two",
completed: false
},
{
id: 3,
title: "Dummy Data Three",
completed: false
},
]
}
},
created() {
eventBus.$on('pass-todo', (**obj**) => {
var parsedobj = JSON.parse(JSON.stringify(obj))
console.log(parsedobj)
})
}
}
</script>
How can I pass the object that is coming from another component through evenBus to the template so I can render it ?? More specific I actually want to add that coming object to the dummyData and rendered it anyway!!

dynamic interpolation computed method name in v-for element in vuejs

In my project vuejs a create a list element with ul li and v-for directive vuejs like this:
<ul>
<li :class="{active: 'isActive+index'}" v-for="(car, index) in cars"></li>
</ul>
Those elements are dynamics. Sometimes are 2 sometimes are 3 or 4 elements
But I need to have a specific logic active class css for each like this:
'isActive+index'
Where this represent a dynamic computed name (already exist). But obviously this code not run and generate basic string word not a link to computed method. I want to execute those computed methods:
computed:
{
isActive1: function ()
{
return myLogic
},
isActive2: function ()
{
return myLogic
},
isActive3: function ()
{
return myLogic
},
isActive4: function ()
{
return myLogic
},
}
How can I link element with dynamic method name for execute computed with vuejs ?
new Vue({
el: '#app',
template: `
<div>
<ul>
<li v-for="(item, index) in cars" :key="index" :class="{ active: statusActive[index] }">
<strong>Car:</strong> {{item.name}} ,
</li>
</ul>
<button #click="changeCars">Change cars</button>
</div>
`,
data() {
return {
cars1: [{
name: "car1",
},
{
name: "car2",
},
{
name: "car3",
},
],
cars2: [{
name: "car1",
},
{
name: "car2",
},
{
name: "car3",
},
{
name: "car4",
},
],
cars3: [{
name: "car1",
},
{
name: "car2",
},
],
carsIndex: 1,
};
},
computed: {
cars() {
return this["cars" + this.carsIndex];
},
statusActive() {
return {
0: this.statusActive0,
1: this.statusActive1,
2: this.statusActive2,
3: this.statusActive3,
};
},
statusActive0() {
return false;
},
statusActive1() {
return true;
},
statusActive2() {
return false;
},
statusActive3() {
return true;
},
},
methods: {
changeCars() {
if (this.carsIndex < 3) {
this.carsIndex++;
} else {
this.carsIndex = 1;
}
},
},
})
.active {
color: red;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.16/vue.js"></script>
<div id="app"></div>
or
new Vue({
el: '#app',
template: `
<div>
<ul>
<li v-for="(item, index) in cars" :key="index" :class="{ active: isActive(index) }">
<strong>Car:</strong> {{item.name}} ,
</li>
</ul>
<button #click="changeCars">Change cars</button>
</div>
`,
data() {
return {
cars1: [{
name: "car1",
},
{
name: "car2",
},
{
name: "car3",
},
],
cars2: [{
name: "car1",
},
{
name: "car2",
},
{
name: "car3",
},
{
name: "car4",
},
],
cars3: [{
name: "car1",
},
{
name: "car2",
},
],
carsIndex: 1,
};
},
computed: {
cars() {
return this["cars" + this.carsIndex];
},
statusActive0() {
return false;
},
statusActive1() {
return true;
},
statusActive2() {
return false;
},
statusActive3() {
return true;
},
},
methods: {
changeCars() {
if (this.carsIndex < 3) {
this.carsIndex++;
} else {
this.carsIndex = 1;
}
},
isActive(index) {
return this["statusActive" + index];
},
},
})
.active {
color: red;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.16/vue.js"></script>
<div id="app"></div>