vue : can't navigate to another page - javascript

i'm new to vue and i have created a dynamic page that gets questions,user name, and categories from API ,every thing is showing fine but my problem is i want to make those dynamic (user name for example) is clickable so when ever i click on it it should take me to user's profile with same name, i have tried to do a clickable div but only the url route changes not the content of the page (it's not taking me to the profile page), also as u see in my code i created a function in methods that saved my clickable user in index and i'm trying to show this user saved in index in my profile page but i didn't figure out how to do it, any help please?
hom.vue
<p>Asked by: </p>
<div id="user" v-for="(user, index) in users"
:key="index">
<div #click="selectedUser(index)">{{ user }}</div>
</div>
export default {
props: {
question1: Object
},
data() {
return {
selectedIndex: null,
};
},
watch: {
question1: {
handler() {
this.selectedIndex = null;
},
},
},
computed: {
users() {
let users = [this.question1.user];
return answers;
},
},
methods: {//here i saved the user that i clicked on in index in order to show it to the profile page
selectedUser(index) {
this.selectedIndex = index;
this.$router.push('/Profile')
},
}
}
profile.vue
<template>
<div class="container" width=800px>
<b-row id="asked-info">
<p>Welcome to the profile of: </p>
<div id="user" v-for="(user, index) in users"
:key="index">
{{ user }}
</div>
</b-row>
</div>
</template>
<script>
export default {
props: {
question1: Object
},
computed: {
users() {
let users = [this.question1.user];
// here i'm not able to figure out how will the user i saved in index (the user i clicked on) will show here
return users;
},
},
}
</script>
</script>

You can pass the index via props to route components.
Your selectedUser function would be like:
selectedUser(index) {
this.$router.push({ path: `/Profile/${index}` })
},
In your Router instance:
routes: [
{
path: '/profile/:index',
component: Profile,
props: true //to specify that you are using props
},
//your other routes
]
Then in the profile.vue, you can access the index by this syntax: this.$route.params.index
But this is just how you pass the index to the profile.vue page, eventually you need to have access to the users array which you have in the hom.vue page, which means that you are sharing data between components (or pages). I strongly suggest you to use Vuex, the Vue state management, a centralized store for your app. You can check out their documentation here: https://vuex.vuejs.org/#what-is-a-state-management-pattern

Related

vue: how to filter data from array

I am new with Vue and I am using API to show data, here in my code when click is performed to category it saves that name of category and redirects user to another page called category.vue, my problem is after redirecting user I want now to show questions with that category name that been received only (as filter), is there a way to do it?
/* this is how i saved data to transfer to category page */
<div class="category" v-for="(categoryy,index) in category(question1)" :key="index">
<router-link :to="`/category/${category(question1)}`"> {{ categoryy }} </router-link>
</div>
category.vue
<template>
<div class="vw">
<p> related question of </p>
<p>{{ this.$route.params.cat }}</p> /* category name i sent appears here */
<ul class="container-question" v-for="(question1,index) in questions" :key="index"
>
/* THE PROBLEM : it shows all questions without filtering */
{{question1.question}}
</ul>
</div>
</template>
<script>
export default {
props:
{
question1: Object
},
data(){
return{
questions: []
}
},
computed:{
category(){
let category = this.$route.params.cat;
return category
}
},
mounted: function(){
fetch('https://opentdb.com/api.php?amount=10&category=9&difficulty=medium&type=multiple',{
method: 'get'
})
.then((response) => {
return response.json()
})
.then((jsonData) => {
this.questions = jsonData.results
})
}
}
</script>
You can first get all the categories with this route https://opentdb.com/api_category.php
As i suspect you already have done on the first vue page.
Then as you do, you pass the id of the category as a route param.
The only thing you seem to miss here is that you have hardcoded the category id 9 in your mounted fetch.
You would want to change this to:
fetch(`https://opentdb.com/api.php?amount=10&category=${this.category}&difficulty=medium&type=multiple`,{
method: 'get'
})
This way uses the computed property you created above from the router param from the previous page.
you have to pass the route param not in a computed property, but directly on the mounted hook,
export default {
mounted() {
fetch(
`https://opentdb.com/api.php?amount=10&category=${this.$route.params.cat}&difficulty=medium&type=multiple`,
{
method: "get",
}
)
.then((response) => response.json())
.then((jsonData) => (this.questions = jsonData.results));
},
};

Why not my vue component not re-rendering?

I have a question why not this component, not re-rendering after changing value so what I'm trying to do is a dynamic filter like amazon using the only checkboxes so let's see
I have 4 components [ App.vue, test-filter.vue, filtersInputs.vue, checkboxs.vue]
Here is code sandbox for my example please check the console you will see the value changing https://codesandbox.io/s/thirsty-varahamihira-nhgex?file=/src/test-filter/index.vue
the first component is App.vue;
<template>
<div id="app">
<h1>Filter</h1>
{{ test }}
<test-filter :filters="filters" :value="test"></test-filter>
</div>
</template>
<script>
import testFilter from "./test-filter";
import filters from "./filters";
export default {
name: "App",
components: {
testFilter,
},
data() {
return {
filters: filters,
test: {},
};
},
};
</script>
so App.vue that holds the filter component and the test value that I want to display and the filters data is dummy data that hold array of objects.
in my test-filter component, I loop throw the filters props and the filterInputs component output the input I want in this case the checkboxes.
test-filter.vue
<template>
<div class="test-filter">
<div
class="test-filter__filter-holder"
v-for="filter in filters"
:key="filter.id"
>
<p class="test-filter__title">
{{ filter.title }}
</p>
<filter-inputs
:value="value"
:filterType="filter.filter_type"
:options="filter.options"
#checkboxChanged="checkboxChanged"
></filter-inputs>
</div>
</div>
<template>
<script>
import filterInputs from "./filterInputs";
export default {
name: "test-filter",
components: {
filterInputs,
},
props:{
filters: {
type: Array,
default: () => [],
},
value: {
type: Array,
default: () => ({}),
},
},
methods:{
checkboxChanged(value){
// Check if there is a array in checkbox key if not asssign an new array.
if (!this.value.checkbox) {
this.value.checkbox = [];
}
this.value.checkbox.push(value)
}
};
</script>
so I need to understand why changing the props value also change to the parent component and in this case the App.vue and I tried to emit the value to the App.vue also the component didn't re-render but if I check the vue dev tool I see the value changed but not in the DOM in {{ test }}.
so I will not be boring you with more code the filterInputs.vue holds child component called checkboxes and from that, I emit the value of selected checkbox from the checkboxes.vue to the filterInputs.vue to the test-filter.vue and every component has the value as props and that it if you want to take a look the rest of components I will be glad if you Did.
filterInpust.vue
<template>
<div>
<check-box
v-if="filterType == checkboxName"
:options="options"
:value="value"
#checkboxChanged="checkboxChanged"
></check-box>
</div>
</template>
<script>
export default {
props: {
value: {
type: Object,
default: () => ({}),
},
options: {
type: Array,
default: () => [],
},
methods: {
checkboxChanged(value) {
this.$emit("checkboxChanged", value);
},
},
}
</script>
checkboxes.vue
<template>
<div>
<div
v-for="checkbox in options"
:key="checkbox.id"
>
<input
type="checkbox"
:id="`id_${_uid}${checkbox.id}`"
#change="checkboxChange"
:value="checkbox"
/>
<label
:for="`id_${_uid}${checkbox.id}`"
>
{{ checkbox.title }}
</label>
</div>
</div>
<template>
<script>
export default {
props: {
value: {
type: Object,
default: () => ({}),
},
options: {
type: Array,
default: () => [],
},
}
methods: {
checkboxChange(event) {
this.$emit("checkboxChanged", event.target.value);
},
},
};
</script>
I found the solution As I said in the comments the problem was that I'm not using v-model in my checkbox input Vue is a really great framework the problem wasn't in the depth, I test the v-model in my checkbox input and I found it re-render the component after I select any checkbox so I search more and found this article and inside of it explained how we can implement v-model in the custom component so that was the solution to my problem and also I update my codeSandbox Example if you want to check it out.
Big Thaks to all who supported me to found the solution: sarkiroka, Jakub A Suplick

I'm trying to populate a second view with data by id from a router-link'd button in my first view. Not sure if I should go through the BaseURL

Is there a way to make this happen by id through the URL? And how do I set it up that the information that populates on the second page is only by that id number? Having a major braindead moment
<div v-for="prize in prizes" :key="prize.name">
<div class="card_individual">
<div class="card-media-container">
<img class="card_image" src="../assets/c5Cor.jpg" alt="people"/></div>
<div class="card-detail-container">
<div class="card_title">Win a {{ prize.name }}</div>
</div>
<div class="modal-footer">
<router-link :to="{ name: 'PriceDetail'}"><button type="button" class="btn btn-primary">{{ cards.btn_text }}</button></router-link>
</div>
</div>
</div>
<script>
import axios from 'axios'
export default {
name: 'Prizes',
data () {
return {
prizes: [],
}
},
mounted () {
this.getPrizes()
},
methods: {
getPrizes () {
axios.get('http://localhost:3000/prizes').then(response => {
this.prizes = response.data.prizes
this.id = response.data.prizes.id
})
}
}
}
You'll need to pass the id as a prop so that the 2nd view can use a different API call to retrieve a prize (price?) by id. The detail view should look something like this:
props: {
prizeId: Number
},
data () {
return {
prize: {},
}
},
mounted () {
this.getPrizeById()
},
methods: {
getPrizeById() {
axios.get('http://localhost:3000/prizebyid/' + this.prizeId).then(response => {
this.prize = response.data.prize
})
}
}
You can pass that id prop in the router-link from the main list like this:
<router-link :to="{ name: 'PriceDetail', params: { prizeId: prize.id }}">
This passes the id as part of the route's path (known as a param). Just make sure that the route definition for the PriceDetail route expects params, and also turns them into props (using props: true), which you can do like this:
{ path: '/<your-path>/:prizeId', name: 'PriceDetail', props: true }
And you need to make sure that your backend is configured to serve up individual data when a route like http://localhost:3000/prizebyid/1000 is visited.
If you want to make it more slick, you can serve up all results when visiting http://localhost:3000/prizes, and individual results when serving http://localhost:3000/prizes/1000, using the prizes route for both. I just used prizebyid route in my example for clarity since it might be easier to configure in the backend at first.

Is it possible to add content to a global Vue component from a single file comp?

I have made a global component that will render the content we want.
This component is very simple
<template>
<section
id="help"
class="collapse"
>
<div class="container-fluid">
<slot />
</div>
</section>
</template>
<script>
export default {
name: 'VHelp',
};
</script>
I use it inside my base template with
<v-help />
I'm trying to add content to this component slot from another single file component using.
<v-help>
<p>esgssthsrthsrt</p>
</v-help>
But this logically create another instance of my comp, with the p tag inside. Not the correct thing I want to do.
So I tried with virtual DOM and rendering function, replacing slot by <v-elements-generator :elements="$store.state.help.helpElements" /> inside my VHelp comp.
The store helpElements is a simple array with objects inside.
{
type: 'a',
config: {
class: 'btn btn-default',
},
nestedElements: [
{
type: 'span',
value: 'example',
},
{
type: 'i',
},
],
},
Then inside my VElementsGenerator comp I have a render function that with render element inside virtual DOM from an object like
<script>
import {
cloneDeep,
isEmpty,
} from 'lodash';
export default {
name: 'VElementsGenerator',
props: {
elements: {
type: Array,
required: true,
},
},
methods: {
iterateThroughObject(object, createElement, isNestedElement = false) {
const generatedElement = [];
for (const entry of object) {
const nestedElements = [];
let elementConfig = {};
if (typeof entry.config !== 'undefined') {
elementConfig = cloneDeep(entry.config);
}
if (entry.nestedElements) {
nestedElements.push(this.iterateThroughObject(entry.nestedElements, createElement, true));
}
generatedElement.push(createElement(
entry.type,
isEmpty(elementConfig) ? entry.value : elementConfig,
nestedElements
));
if (typeof entry.parentValue !== 'undefined') {
generatedElement.push(entry.parentValue);
}
}
if (isNestedElement) {
return generatedElement.length === 1 ? generatedElement[0] : generatedElement;
}
return createElement('div', generatedElement);
},
},
render(createElement) {
if (this.elements) {
return this.iterateThroughObject(this.elements, createElement);
}
return false;
},
};
</script>
This second method is working well but if I want to render complex data, the object used inside the rendering function is very very long and complex to read.
So I'm trying to find another way to add content to a global component used inside a base layout only when I want it on a child component.
I can't use this VHelp component directly inside children comps because the HTML page architecture will be totally wrong.
I'm wondering if this is possible to add content (preferably HTML) to a component slot from a single file comp without re-creating a new instance of the component?
Furthermore I think this is very ugly to save HTML as string inside a Vuex store. So I don't even know if this is possible and if I need to completely change the way I'm trying to do this.
Any ideas ?
In the store, you should only store data and not an HTML structure. The way to go with this problem would be to store the current state of the content of the v-help component in the store. Then, you would have a single v-help component with a slot (like you already proposed). You should pass different contents according to the state in the store. Here is an abstract example:
<v-help>
<content-one v-if="$store.state.content === 'CONTENT_ONE' />
<content-two v-else-if="$store.state.content === 'CONTENT_TWO' />
<content-fallback v-else />
</v-help>
Child element somewhere else:
<div>
<button #click="$store.commit('setContentToOne')">Content 1</button>
</div>
Vuex Store:
state: {
content: null
},
mutations: {
setContentToOne(state) {
state.content = 'CONTENT_ONE';
}
}
Of course it depends on your requirements and especially on how many different scenarios are used if this is the best way to achieve this. If I understood you correctly, you are saving help elements to the store. You could also save an array of currently selected help elements in there and just display them directly in the v-help component.
EDIT:
Of course you can also just save the static component (or its name) in the store. Then, you could dynamically decide in the child components, which content is shown in v-help. Here is an example:
<v-help>
<component :is="$store.state.helpComponent" v-if="$store.state.helpComponent !== null" />
</v-help>
Test Component:
<template>
test component
</template>
<script>
export default {
name: 'test-component'
};
</script>
Child element somewhere else (variant 1, storing the name in Vuex):
<div>
<button #click="$store.commit('setHelpComponent', 'test-component')">Set v-help component to 'test-component'</button>
</div>
Child element somewhere else (variant 2, storing the whole component in Vuex):
<template>
<button #click="$store.commit('setHelpComponent', testComponent)">Set v-help component to testComponent (imported)</button>
</template>
<script>
import TestComponent from '#/components/TestComponent';
export default {
name: 'some-child-component',
computed: {
testComponent() {
return TestComponent;
}
}
};
</script>
Child element somewhere else (variant 3, storing the name, derived from the imported component, in Vuex; I would go with this variant):
<template>
<button #click="$store.commit('setHelpComponent', testComponentName)">Set v-help component to 'test-component'</button>
</template>
<script>
import TestComponent from '#/components/TestComponent';
export default {
name: 'some-child-component',
computed: {
testComponentName() {
return TestComponent.name;
}
}
};
</script>
Vuex Store:
state: {
helpComponent: null
},
mutations: {
setHelpComponent(state, value) {
state.helpComponent = value;
}
}
See also the documentation for dynamic components (<component :is=""> syntax): https://v2.vuejs.org/v2/guide/components.html#Dynamic-Components

Vue JS - Problem with computed property not updating

I am quite new with VueJS and I have been having trouble lately with some computed properties which do not update as I would like. I've done quite some research on Stack Overflow, Vue documentation and other ressources but i haven't found any solution yet.
The "app" is basic. I've got a parent component (Laundry) which has 3 child components (LaundryMachine). The idea is to have for each machine a button which displays its availability and updates the latter when clicked on.
In order to store the availability of all machines, I have a data in the parent component (availabilities) which is an array of booleans. Each element corresponds to a machine's availability.
When I click on the button, I know the array availibities updates correctly thanks to the console.log. However, for each machine, the computed property "available" does not update is I would want it to and I have no clue why.
Here is the code
Parent component:
<div id="machines">
<laundry-machine
name="AA"
v-bind:machineNum="0"
v-bind:availableArray="this.availabilities"
v-on:change-avlb="editAvailabilities"
></laundry-machine>
<laundry-machine
name="BB"
v-bind:machineNum="1"
v-bind:availableArray="this.availabilities"
v-on:change-avlb="editAvailabilities"
></laundry-machine>
<laundry-machine
name="CC"
v-bind:machineNum="2"
v-bind:availableArray="this.availabilities"
v-on:change-avlb="editAvailabilities"
></laundry-machine>
</div>
</div>
</template>
<script>
import LaundryMachine from './LaundryMachine.vue';
export default {
name: 'Laundry',
components: {
'laundry-machine': LaundryMachine
},
data: function() {
return {
availabilities: [true, true, true]
};
},
methods: {
editAvailabilities(index) {
this.availabilities[index] = !this.availabilities[index];
console.log(this.availabilities);
}
}
};
</script>
Child component:
<template>
<div class="about">
<h2>{{ name }}</h2>
<img src="../assets/washing_machine.png" /><br />
<v-btn color="primary" v-on:click="changeAvailability">
{{ this.availability }}</v-btn>
</div>
</template>
<script>
export default {
name: 'LaundryMachine',
props: {
name: String,
machineNum: Number,
availableArray: Array
},
methods: {
changeAvailability: function(event) {
this.$emit('change-avlb', this.machineNum);
console.log(this.availableArray);
console.log('available' + this.available);
}
},
computed: {
available: function() {
return this.availableArray[this.machineNum];
},
availability: function() {
if (this.available) {
return 'disponible';
} else {
return 'indisponible';
}
}
}
};
</script>
Anyway, thanks in advance !
Your problem comes not from the computed properties in the children, rather from the editAvailabilities method in the parent.
The problem is this line in particular:
this.availabilities[index] = !this.availabilities[index];
As you can read here, Vue has problems tracking changes when you modify an array by index.
Instead, you should do:
this.$set(this.availabilities, index, !this.availabilities[index]);
To switch the value at that index and let Vue track that change.

Categories

Resources