Vue : Accessing Nested Object Component's Values - javascript

I have problems accessing this "name" property on the component. I can only access it statically.
<template>
<div class="col-md-12">
<p
v-for="channel in channels"
:key="channel.id"
class="channel"
:class="{ 'active': channel.id == activeChannel }"
#click="setChannel(channel.id)">
{{ channel.users[0].name }}
</p>
</div>
</template>
Here is an Image of my Vue Devtools
So I have an v-for loop over channels, and I want to: Access the Usernames for each channel (if it is not my own preferably as "username" is set on my own i think its easy to exclude it right?) So that in the end In Channel 1 when there are 2 Users , I want to show the corresponding username, so the "other username", the one i am chatting with, and he should see my name that is the initial goal.
I thought of doing something like this:
<template>
<div class="col-md-12">
<p
v-for="channel in channels"
:key="channel.id"
class="channel"
:class="{ 'active': channel.id == activeChannel }"
#click="setChannel(channel.id)">
<!-- {{ channel.users[0].name }} -->
<span v-for="user,key in channel">{{key}}</span>
</p>
</div>
it at least displays the content of the channels object for each channel, but something like this isnt gonna work: key.user.name , unfortunately im stuck here. please help :)
edit: here is a dd() of the view
click
EDIT 2: Parent Data Provided:
//chat-app.blade.php
<div id="app">
<div class="container">
<div class="row">
<div class="col-md-8 col-md-offset-2">
<div class="panel panel-default">
<div class="panel-heading">Chats</div>
<vue-chat :channels="{{ $channels }}" ></vue-chat>
</div>
</div>
</div>
</div>
</div>
</div>
//<vue-chat> component
<template>
<div class="chat">
<div class="container">
<div class="row">
<div class="col-md-3">
<vue-chat-channels
:channels="channels"
:active-channel="activeChannel"
#channelChanged="onChannelChanged"
:username="sername"
></vue-chat-channels>
</div>
<div class="col-md-3">
<vue-chat-messages :messages="messages"></vue-chat-messages>
</div>
<div class="col-md-3">participants</div>
</div>
<div class="message-input-wrapper col-md-12"><vue-chat-new-message :active-channel="activeChannel"
:username="username"></vue-chat-new-message></div>
</div>
</div>
</template>
<script>
export default {
props: ["channels"],
data() {
return {
activeChannel: this.channels[0].id,
messages: [],
username: ''
};
},
methods: {
fetchMessages() {
let endpoint = `/channels/${this.activeChannel}/messages`;
axios.get(endpoint).then(({ data }) => {
this.messages = data;
});
},
onChannelChanged(id) {
this.activeChannel = id;
this.fetchMessages();
}
},
created() {
this.fetchMessages();
axios.get('/userfetch').then( ({data}) => {
console.log("Current User: "+data.name);
this.username = data.name;
});
console.log(this.channels[0].name);
// for (let channel of this.channels) {
this.channels.forEach(channel => {
// Channelname
window.Echo.channel('presence-'+channel.name)
.listen('MessageSent', (channel) => {
console.log(channel.data.message);
this.messages.push({ message: channel.data.message, author_username: channel.data.author_username});
if (this.activeChannel == channel.id) {
console.log("received message");
}
});
});
}
};
</script>
<style>
</style>
//ChatController.php
public function index()
{
$channels = Channel::with('users')->whereHas('users', function($q) {
$q->where('user_id',Auth::id());
})->get();
$user = Auth::user()->name;
return view('chat-app' , compact('channels','user'));
}
Short Explanation: ChatController returns the blade view, which has the data channels and user (my username) , and then vue comes into play which should pass down the prop of my username but i couldnt get it to work just yet

So you need to access users in every channel.
You can try like this:
<div class="col-md-12">
<p
v-for="channel in channels"
:key="channel.id"
class="channel"
:class="{ 'active': channel.id == activeChannel }"
#click="setChannel(channel.id)">
<span v-for="user in channel.users">
{{ user.name }}
</span>
</p>
</div>
This should work. If you have errors provide it here.
If you need to compare every user you can do it simply with v-if:
<span v-for="user in channel.users">
<span v-if="user.name === parentdata">
{{ user.name }}
</span>
</span>

Related

Vue CLI - TypeError: Cannot read properties of undefined (reading '1')

I'm beginner in VueJS and hoping for your help.
I'm trying to create Weather forecast app based on OpenWeatherMap API.
The concept is such that:
Enter some location to input on homepage and click to search button. (in my code it's a component Search.vue)
Redirecting to another page and show results - current weather and forecast for next 6 days. (component Weather.vue)
I created function with two consistent fetch calls. First fetch taking entered input query and return needed data from Current Weather Data API. After that, function run second fetch to One Call API based on latitude longitude from first fetch.
Everything is working and showing fine, but i don't undestand why i have an error Uncaught (in promise) TypeError: Cannot read properties of undefined (reading '1') in console:
Сan someone know how to fix this error?
My Search.vue (homepage) component:
<template>
<div class="row">
<div class="search-col col">
<div class="search-box">
<input
type="text"
class="search-bar"
placeholder="Location"
v-model="query">
<router-link :to="{name: 'DetailedWeather', params: { query: query }}" class="btn-search">
<i class="fas fa-search"></i>
</router-link>
</div>
</div>
</div>
</template>
My Weather.vue (weather results showing page) component:
<template>
<div class="row" v-if="typeof weather.main != 'undefined'">
<div class="weather-col col">
<div class="weather-app">
<div class="weather-box">
<div class="weather-top-info">
<div class="clouds-level"><span class="icon"><i class="fas fa-cloud"></i></span> {{ weather.clouds.all }}%</div>
<div class="humidity"><span class="icon"><i class="fas fa-tint"></i></span> {{ weather.main.humidity }}%</div>
</div>
<div class="weather-main-info">
<div class="temp-box">
<div class="temp-main">{{ Math.round(weather.main.temp) }}</div>
<div class="temp-inner-box">
<div class="temp-sign">°C</div>
<div class="temp-max"><span class="icon"><i class="fas fa-long-arrow-alt-up"></i></span> {{ Math.round(weather.main.temp_max) }}°</div>
<div class="temp-min"><span class="icon"><i class="fas fa-long-arrow-alt-down"></i></span> {{ Math.round(weather.main.temp_min) }}°</div>
</div>
</div>
<div class="weather-desc">{{ weather.weather[0].description }}</div>
<div class="weather-icon">
<img :src="'http://openweathermap.org/img/wn/'+ weather.weather[0].icon +'#4x.png'">
</div>
</div>
<div class="weather-extra-info">
<div>Feels like: <span>{{ Math.round(weather.main.feels_like) }}°C</span></div>
<div>Sunrise: <span>{{ weather.sys.sunrise }}</span></div>
<div>Sunset: <span>{{ weather.sys.sunset }}</span></div>
</div>
</div>
</div>
</div>
<div class="forecast-col col">
<div class="row">
<div class="forecast-item-col col" v-for="day in forecastDays" :key="day">
<div class="forecast-box">
<div class="forecast-date">{{ forecast.daily[day].dt }}</div>
<div class="forecast-temp">{{ Math.round(forecast.daily[day].temp.day) }}°C</div>
<div class="forecast-icon"><img :src="'http://openweathermap.org/img/wn/'+ forecast.daily[day].weather[0].icon +'#2x.png'"></div>
</div>
</div>
</div>
</div>
</div>
<div class="row">
<div class="actions-col col">
<router-link :to="{name: 'Search'}" class="btn btn-default">
Back to search
</router-link>
</div>
</div>
</template>
<script>
export default {
name: 'Weather',
props: ['query'], //getting from homepage
data() {
return {
api_key:'b7fe640e9a244244a6f806f3a6cbf5fc',
url_base:'https://api.openweathermap.org/data/2.5/',
forecastDays: 6,
weather: {},
forecast: {}
}
},
methods: {
fetchWeather(){
// first call
fetch(`${this.url_base}weather?q=${this.query}&units=metric&appid=${this.api_key}`)
.then(response =>{ return response.json() }).then(this.setResults);
},
setResults(results){
this.weather = results;
// consistent second call
fetch(`${this.url_base}onecall?lat=${results.coord.lat}&lon=${results.coord.lon}&exclude=current,minutely,hourly,alerts&units=metric&appid=${this.api_key}`)
.then(data =>{ return data.json() }).then(this.setForecast);
},
setForecast(results){
this.forecast = results
},
},
created() {
this.fetchWeather();
}
</script>
My router/index.js file:
import { createRouter, createWebHashHistory } from 'vue-router'
import Search from '../components/Search.vue'
import Weather from '../components/Weather.vue'
const routes = [
{
path: '/',
name: 'Search',
component: Search
},
{
path: '/detailed-weather',
name: 'DetailedWeather',
component: Weather,
props: true
}
]
const router = createRouter({
history: createWebHashHistory(),
routes
})
export default router
From what I guess (given the code and the error), there might be an issue with the objects you are receiving from the API.
The error message suggests you are trying to read something from an array at a specific index that is undefined.
The only occurrence in your code that could cause this error is from the template, where you are reading, for example:
{{ forecast.daily[day].dt }}
{{ Math.round(forecast.daily[day].temp.day) }}
I can't tell exactly which one is it, but try to double check the shape of the objects you are working with.

Vuejs SearchBar ||using filter method not working

I quite new to Vue js I am trying to use the computed method to create a search bar to only search through the name but I'm getting "this.info.filter" is not a function
<template>
<div class="container">
<input type="text" v-model="search" placeholder="Search by name">
<div class="content" v-for="student in filterName " v-bind:key="student.id">
<img class="image" :src="student.pic" alt="">
<div class="student-info">
<h1 class="info">{{student.firstName +" "+ student.lastName}}</h1>
<div class="infomation">
<p class="cop">{{ student.company }}</p>
<p class="ski">{{ student.skill }}</p>
<p class="email">{{ student.email }}</p>
<p class="grade">{{ student.grades }}</p>
</div>
</div>
</div>
</div>
</template>
<script>
import axios from "axios";
export default {
name: "Student.vue",
data() {
return {
students: '',
search: ''
}
},
mounted() {
axios
.get('https://api.hatchways.io/assessment/students')
.then((res) => {
this.students = (res.data.students)
})
},
computed :{
filterName:function (){
return this.info.filter((student)=>{
return student.company.matcth(this.search);
})
}
}
}
</script>
</style>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
First time using StackOverflow too please ignore the errors
I don't see a declaration/initialization for this.info anywhere in your code.
The reason you're getting that error is because filter is trying to run on an undefined variable (this.info is undefined).
You might want to initialize that to an empty array within data.

Bind element inside a for loop Vue not working properly

In the following Vue Component I want to loop through dwarfs array. And as long as I am in the current component, everything is fine (TEST) and also all the following properties are correct.
Currenct_Component.vue :
<template>
<div>
<h2>Stamm: {{ tribeName }}</h2>
<div class="card-container">
<div class="card" style="width: 18rem;" v-for="dwarf in dwarfs" :key="dwarf.name">
<!-- TEST -->
<p>{{dwarf}}</p>
<!-- CHILD COMPONENT -->
<app-modal
:showModal="showModal"
:targetDwarf="dwarf"
#close="showModal = false"
#weaponAdded="notifyApp"
/>
<!-- <img class="card-img-top" src="" alt="Card image cap">-->
<div class="card-body">
<h3 class="card-title" ref="dwarfName">{{ dwarf.name }}</h3>
<hr>
<ul class="dwarf-details">
<li><strong>Alter:</strong> {{ dwarf.age }}</li>
<li><strong>Waffen:</strong>
<ul v-for="weapon in dwarf.weapons">
<li><span>Name: {{ weapon.name }} | Magischer Wert: {{ weapon.magicValue }}</span></li>
</ul>
</li>
<li><strong>Powerfactor:</strong> {{ dwarf.weapons.map(weapon => weapon.magicValue).reduce((accumulator, currentValue) => accumulator + currentValue) }}</li>
</ul>
<button class="card-button" #click="showModal = true"><span class="plus-sign">+</span> Waffe</button>
</div>
</div>
</div>
<button id="backBtn" #click="onClick">Zurück</button>
</div>
</template>
<script>
import Modal from './NewWeaponModal.vue';
export default {
data() {
return {
showModal: false,
}
},
components: { appModal : Modal },
props: ['tribeName', 'dwarfs'],
methods: {
onClick() {
this.$emit('backBtn')
},
notifyApp() {
this.showModal = false;
this.$emit('weaponAdded');
}
},
}
</script>
But when I bind the element dwarf to the Child Component <app-modal/> it changes to the next dwarf in the array dwarfs (TEST) - (So as the result when i add a new weapon in the modal-form it gets added to the second dwarf...):
Child_Component.vue :
<template>
<div>
<div class="myModal" v-show="showModal">
<div class="modal-content">
<span #click="$emit('close')" class="close">×</span>
<h3>Neue Waffe</h3>
<!-- TEST -->
<p>{{ targetDwarf }}</p>
<form>
<input
type="text"
placeholder="Name..."
v-model="weaponName"
required
/>
<input
type="number"
placeholder="Magischer Wert..."
v-model="magicValue"
required
/>
<button #click.prevent="onClick">bestätigen</button>
</form>
</div>
</div>
</div>
</template>
<script>
export default {
data() {
return {
weaponName: '',
magicValue: '',
}
},
props: ['showModal', 'targetDwarf'],
methods: {
onClick() {
if(this.weaponName !== '' &&
Number.isInteger(+this.magicValue)) {
let newData = {...this.dwarf};
newData['weapons'] = [
...this.dwarf['weapons'],
{
"name": this.weaponName,
"magicValue": Number.parseInt(this.magicValue)
},
];
this.$http.post("https://localhost:5019/api", newData)
.then(data => data.text())
.then(text => console.log(text))
.catch(err => console.log(err));
this.$emit('weaponAdded');
} else {
alert('You should fill all fields validly')
}
},
}
}
</script>
It looks like you have the <app-modal/> component inside of the v-for="dwarf in dwarfs" loop, but then the control for showing all of the modal components created by that loop is just in one variable: showModal. So when showModal is true, the modal will show each of the dwarfs, and I'm guessing the second dwarf's modal is just covering up the first one's.
To fix this, you could move the <app-modal /> outside of that v-for loop, so there's only one instance on the page, then as part of the logic that shows the modal, populate the props of the modal with the correct dwarf's info.
Something like this:
<div class="card-container">
<div class="card" v-for="dwarf in dwarfs" :key="dwarf.name">
<p>{{dwarf}}</p>
<div class="card-body">
<button
class="card-button"
#click="() => setModalDwarf(dwarf)"
>
Waffe
</button>
</div>
</div>
<!-- Move outside of v-for loop -->
<app-modal
:showModal="!!modalDwarfId"
:targetDwarf="modalDwarfId"
#close="modalDwarfId = null"
#weaponAdded="onDwarfWeaponAdd"
/>
</div>
export default {
//....
data: () => ({
modalDwarfId: null,
)},
methods: {
setModalDwarf(dwarf) {
this.modalDwarfId = drawf.id;
},
onDwarfWeaponAdd() {
//...
}
},
}
You could then grab the correct dwarf data within the modal, from the ID passed as a prop, or pass in more granular data to the modal so it's more "dumb", which is the better practice so that the component isn't dependent on a specific data structure. Hope that helps
Courtesy of #Joe Dalton's answer, a bit alternated for my case:
<div class="card" style="width: 18rem;" v-for="dwarf in dwarfs" :key="dwarf.name">
...
<button class="card-button" #click="setModalDwarf(dwarf)"><span class="plus-sign">+</span> Waffe</button>
<div>
<app-modal
:showModal="showModal"
:targetDwarf="currentDwarf"
#close="showModal = false"
#weaponAdded="notifyApp"
/>
<script>
import Modal from './NewWeaponModal.vue';
export default {
data() {
return {
showModal: false,
currentDwarf: null,
}
},
components: { appModal : Modal },
props: ['tribeName', 'dwarfs'],
methods: {
setModalDwarf(dwarf) {
this.currentDwarf = dwarf;
this.showModal = true;
},
...
}
</script>

Understanding how to use pagination in Bootstrap-vue

I have written the following code:
<div>
<div id="app" class="container">
<div class="grid">
<article v-for="tool in tools">
<div class="title">
<h3>{{capitalizeFirstLetter(tool.name)}}</h3>
</div>
<div class="description">
{{tool.description}}
</div>
<br>
<div>
<toggle-button :value=tool.status color="#82C7EB" :width=100 :height=30 :sync="true" :labels="{checked: 'Following', unchecked: 'Unfollowing'}" #change="onChange(tool)"/>
</div>
</article>
</div>
</div>
</div>
Recently I started using Bootstrap-Vue. I'm trying to figure out how to add pagination on the bottom.
I'm not sure how aria-controls works. My goal is to have 9 blocks of tools on each page. How should I add the pagination so I could move to the next 9 blocks of tools?
Since it wasn't clear if you needed client-side pagination or serverside i made an example of both in the snippet.
For simplicity I've made it 3 per page, but you could change it to 9.
The first one gets the initial page on load, and then calls the API every time the page changes by using a watcher, that calls a method with the new page which then retrieves that place and replaces our old data with the new.
The second one loads all the data on page load, and instead slices the data array based on the per_page property, so that only the items for that page is shown.
For this I've used a computed property which automatically updates based on the properties used inside it.
Both paginations have aria-controls defined with the id of the container of our page elements. This is used to tell screen readers what elements the pagination changes.
The classes row, col-*, border, mx-auto, h2 and text-center is classes used for styling and layout and isn't part of the actual solution, so you can freely change or remove them.
new Vue({
el: '#app',
computed: {
pagination2CurrentItems() {
const startIndex = (this.pagination2.current_page - 1) * this.pagination2.per_page;
const endIndex = startIndex + this.pagination2.per_page;
return this.pagination2.items.slice(startIndex, endIndex)
}
},
created() {
this.getPagination1Data()
this.getPagination2Data()
},
filters: {
capitalizeFirstLetter(value) {
return value.charAt(0).toUpperCase() + value.slice(1)
}
},
data() {
return {
pagination1: {
items: [],
per_page: 3,
total_rows: 0,
current_page: 1
},
pagination2: {
items: [],
per_page: 3,
total_rows: 0,
current_page: 1
}
}
},
methods: {
getPagination1Data(page = 1) {
fetch(`https://reqres.in/api/unknown?page=${page}&per_page=3`)
.then((response) => {
return response.json();
})
.then((data) => {
this.pagination1.total_rows = data.total;
this.pagination1.items = data.data;
});
},
getPagination2Data() {
/*
This endpoint only has 12 items total,
so this will get all in one call
*/
fetch(`https://reqres.in/api/unknown?per_page=12`)
.then((response) => {
return response.json();
})
.then((data) => {
this.pagination2.total_rows = data.total;
this.pagination2.items = data.data;
});
}
},
watch: {
'pagination1.current_page'(newPage) {
this.getPagination1Data(newPage)
}
}
})
<script src="https://unpkg.com/vue#2.6.11/dist/vue.min.js"></script>
<script src="https://unpkg.com/bootstrap-vue#2.5.0/dist/bootstrap-vue.js"></script>
<link href="https://unpkg.com/bootstrap#4.4.1/dist/css/bootstrap.min.css" rel="stylesheet"/>
<link href="https://unpkg.com/bootstrap-vue#2.5.0/dist/bootstrap-vue.css" rel="stylesheet"/>
<div id="app" class="container">
<div id="tools_list_1" class="row">
<div class="col-12 h2 text-center">
Server pagination
</div>
<article class="col-3 mx-auto border" v-for="tool in pagination1.items">
<div class="title">
<h3>{{ tool.name | capitalizeFirstLetter }}</h3>
</div>
<div class="description">
{{ tool.pantone_value }}
</div>
</article>
</div>
<div class="row mt-2">
<div class="col-12">
<b-pagination
v-model="pagination1.current_page"
:per-page="pagination1.per_page"
:total-rows="pagination1.total_rows"
aria-controls="tools_list_1"
>
</b-pagination>
</div>
</div>
<div id="tools_list_2" class="row">
<div class="col-12 h2 text-center">
Client pagination
</div>
<article class="col-3 mx-auto border" v-for="tool in pagination2CurrentItems">
<div class="title">
<h3>{{ tool.name | capitalizeFirstLetter }}</h3>
</div>
<div class="description">
{{ tool.pantone_value }}
</div>
</article>
</div>
<div class="row mt-2">
<div class="col-12">
<b-pagination
v-model="pagination2.current_page"
:per-page="pagination2.per_page"
:total-rows="pagination2.total_rows"
aria-controls="tools_list_2"
>
</b-pagination>
</div>
</div>
</div>
If you are interested in server side pagination with url changes of query params (using vue-router in history mode) and you need browser history (back/forward) to work properly, you can check my answer to a similar question. I think it can be adapted to not use b-table.
Place pagination number in url Vuejs

How to change html element or pass data based on the page in AngularJs routing?

I created a component called <bradcam-area>, this section should be displaying titles of each page. I thought of using
So here is the HTML:
<div class="bradcam_area bradcam_bg_1">
<div class="container">
<div class="row">
<div class="col-xl-12">
<div class="bradcam_text text-center">
<h3> Change this based on the page </h3>
</div>
</div>
</div>
</div>
</div>
And here is the TS file:
export class BradcamAreaComponent implements OnInit {
// Titles
titles = {
about: 'About Me',
services: 'Here is what I provide',
portfolio: 'My awesome work here',
contact: 'Get in touch'
}
constructor() { }
ngOnInit() {
}
}
I researched can't figure out what should I do.
here is the about page for example:
<app-header></app-header>
<bradcam-area></bradcam-area>
<!-- You can add extra content under this comment -->
<about-area></about-area>
<app-footer></app-footer>
Instead of using *ngIf (seeing your own solution), you could simply do this :
<h3> {{ titles[router.url] }} </h3>
Much more compact, fast and easier to maintain and update.
I found a solution. I used the ngIf method <h3 *ngIf="router.url === '/about'">
and in the <bradcam-area> Ts file I added these
private router: Router;
constructor(router: Router) {
this.router = router;
}
and here is the Final HTML template of <bradcam-area>
<div class="bradcam_area bradcam_bg_1">
<div class="container">
<div class="row">
<div class="col-xl-12">
<div class="bradcam_text text-center">
<h3 *ngIf="router.url === '/about'"> {{ titles.about }} </h3>
<h3 *ngIf="router.url === '/services'"> {{ titles.services }} </h3>
<h3 *ngIf="router.url === '/portfolio'"> {{ titles.portfolio }} </h3>
<h3 *ngIf="router.url === '/contact'"> {{ titles.contact }} </h3>
</div>
</div>
</div>
</div>
</div>

Categories

Resources