AlpineJS async call to render array of objects - arraylist

Having the following snippet trying to fill an array of object with async alpine call but I can not get any result. Hier is what I try.
HTML:
<div x-init="experts.retrieveList" x-data="experts.list">
<ul>
<template x-for="item in experts.list" :key="item">
<li>
<div x-text="await item.address" ></div>
</li>
</template>
</ul>
</div>
external JS file
window.experts = {
apiUrl: 'http://bdb.local:8991/api/',
data: [],
list: [],
expertsForm: null,
retrieveList: () => {
const membersUrl = `${experts.apiUrl}members?include=user,association,affiliate`;
experts.apiCalls(membersUrl)
},
filterByParams: () => {
},
apiCalls: async (url) => {
let response = await fetch(url);
experts.list = await response.json()
return experts.list;
},
}
What is wrong in this case?

There are a few errors in this code:
You cannot use just a part of an Alpine.js component in the x-data="experts.list". If you don't define data variables directly in x-data, then it must be a real component that returns data and methods.
You cannot use an object as the key. It must be a string or number, like item.id or something like this.
The x-text="await item.address" seems incorrect. item.address should be a string, that has been already downloaded from the API.
In the component you need to use the this. prefix to access properties and methods.
Assuming your API returns the correct data format, something like this should work:
<div x-data="experts">
<ul>
<template x-for="item in list" :key="item.id">
<li>
<div x-text="item.address"></div>
</li>
</template>
</ul>
</div>
And the component in an external file:
const experts = {
apiUrl: 'http://bdb.local:8991/api/',
data: [],
list: [],
expertsForm: null,
init() {
const membersUrl = `${experts.apiUrl}members?include=user,association,affiliate`
this.apiCalls(membersUrl)
},
filterByParams() {
},
async apiCalls(url) {
let response = await fetch(url)
this.list = await response.json()
},
}
The init() method is executed automatically by Alpine.js.

Related

V-for not working with dynamic data in template

My template has following:
<ul id="example-1">
<li v-for="item in getMenus" :key="item.id">
{{ item.name }}
</li>
</ul>
methods:{
async getMenus() {
this.$axios.setHeader('Content-Type', 'application/json', ['get'])
this.$axios.setHeader(
'Authorization',
'Bearer ' + this.$store.state.auth.Token
)
const roleId = this.$store.state.auth.role.roleId
const url = `/role/${roleId}/menu`
let data = ''
// eslint-disable-next-line vue/no-async-in-computed-properties
const pal = await this.$axios
.$get(url, JSON.stringify(roleId))
.then(function(resp) {
data = resp.data
})
if (pal) {
// eslint-disable-next-line no-console
console.log('hi')
}
return data
}
}
}
Above mentioned is my code. I checked my api its returing data. If i put directly my data as harcoded value then it works, if I use api then it doesnot work. I looke dinto console also that is also clear. I am new to vue. Any help will be highly appreciated.
You can't use async methods in v-for. Define an array in data section of a component and write results in the array at the end of getMenus function. You should call getMenus at some place in your code (for instance in mounted hook):
<li v-for="item in menuList" :key="item.id">
...
// in a component code
data: {
return {
menuList: []
}
},
mounted () {
// if you don't have any initialization after this call you can call it without await
getMenus()
},
methods:{
async getMenus() {
...
// getting results
const { data: menuList } = await this.$axios
.$get(url, JSON.stringify(roleId))
this.menuList = menuList
}
This happens because inside async getMenus method you are returning data before it is even assigned a value. A better way to resolve this issue would be to set a variable in data options like:
data() {
return {
loading: false,
items: [] // This will hold all the getMenus() data
}
},
and inside getMenus update items array like:
created() {
this.getMenus();
},
methods: {
async getMenus() {
this.loading = true;
// All other logic here...
this.$axios.$get(url, JSON.stringify(roleId))
.then(resp => {
this.loading = false;
this.items = resp.data; // Set the response data here...
})
.catch(error => {
this.loading = false;
console.log(error);
})
}
}
and then update your template like:
<li v-for="item in items" :key="item.id">
{{ item.name }}
</li>
In case, your async method is going to take some time to finish you can show a loading text or icon so that user know that at least something is happening instead of looking at a blank screen like:
<template v-if="loading">
Loading...
</template>
<template v-else>
<ul id="example-1">
<li v-for="item in items" :key="item.id">
{{ item.name }}
</li>
</ul>
</template>

How can i get properties from template

Can i get properties from template before send request to server about the data?
Here's my template:
<box-component inline-template>
<div>
<div class="loading" v-if="Loading"> Loading ... </div>
<div v-if="Result">
<ul>
<li> {{ Result.FirstProp }} </li>
<li> {{ Result.FourthProp }} </li>
</ul>
</div>
</div>
And this is my component:
let BoxComponent = {
data() {
return {
Result: null,
Loading: true
};
},
created: function () {
this.Update();
},
methods: {
Update: function () {
this.Loading = true;
axios.post("#SERVER_URL#")
.then(response => {
this.Loading = false;
this.Result = response.data;
});
}
}
}
Vue.component('box-component', BoxComponent);
It's works fine! The problem is the server response contain much more data. Server response:
{
"FirstProp": "first",
"SecondProp": "second"
...
"HundredthProp": "hudredth"
}
I can't modify the response (other project use this too). But if i am able to send property list from the template (which are in this case FirstProp and FourthProp) the server side give me the filtered response which contains only these properties becouse the optimal response look like this:
{
"FirstProp": "first",
"FourthProp": "fourth"
}
So, the question is: how can i do that?
Can i find it in the Vue object?
I can load template as a string variable, but i want to avoid regex hack.
Update:
In this case, BoxTemplate call server side without "filters"
This is the optimal case, when js get the variables, that the template use, and call with them the server side. In this case, in the response there are only that variables, what template really use
I don't know how is set up your back-end but you can have additional property within your data:
data() {
return {
listOfProperty: [
'FirstProp',
'FourthProp',
],
...
And use list of it to send data to server.

How does vuejs react to component data updated asynchronously

I am very new with vuejs and recently started to try to replace some old jquery code that I have and make it reactive with vuejs. The thing is I have a component that gets information from a nodejs server via socket.io asynchronously.
When I get the data and update my component's data I see the changes when I console log it but it does not change the DOM the way I want it to do.
What is the proper way to grab data asynchronously and use it inside a component? I post some parts of my code so you can see it. I will appreciate any advice you can give me. Thanks in advance!
Vue.component('chat', {
data() {
return {
chat: null,
commands: [],
chatOpened: false,
}
},
props: [
'io',
'messages',
'channels',
'connectChat',
'roomChat',
'user',
'userId',
'soundRoute',
],
methods: {
openChat() {
this.chatOpened = true;
},
closeChat() {
this.chatOpened = false;
},
},
created() {
this.chat = this.$io.connect(this.connectChat);
this.commands.push('clear');
let self = this;
$.each(this.channels, function(index, value) {
self.chat.emit('join', {room: index, user: self.user, userId: self.userId}, function(err, cb) {
if (!err) {
users = cb.users;
messages = cb.messages;
if (messages.length > 0) {
self.channels[index].loaded = true;
}
//some more code
}
});
});
console.log(this.channels);
},
template: `
<div>
<div id="container-chat-open-button" #click="openChat" :class="{hide : chatOpened}">
<div>+90</div>
<i class="fas fa-comment-alt"></i>
</div>
<div id="container-chat" class="chat__container" :class="{open : chatOpened}">
<div id="container-chat-close-button" #click="closeChat">
<span>
<div>
<i class="fas fa-comment-alt"></i>
#{{ messages.chat_lobby_icon_title }}
</div>
<i class="icon-arrowdown"></i>
</span>
</div>
<div id="alert-chat" class="chat__container-notifications animated flash"></div>
<div class="row">
<ul>
<li v-for="channel in channels" v-show="channel.loaded === true">Channel loaded</li>
</ul>
</div>
</div>
</div>
`
});
I would expect to see the list of channels with messsages but instead I don't see the list even thought I see my channels with the loaded attribute set to true (by default they all have this attribute set to false).
My guess is that it's this part that is not working as expected.
if (messages.length > 0) {
self.channels[index].loaded = true;
}
The reactive way of doing this is by setting the full object again.
Vue.set(self.channels, index, {
...self.channels[index],
loaded: true
}
EDIT 1:
this.channels.forEach((channel) => {
this.chat.emit('join', {room: index, user: self.user, userId: self.userId}, (err, cb) => {
if (!err) {
users = cb.users;
messages = cb.messages;
if (messages.length > 0) {
Vue.set(self.channels, index, {
...self.channels[index],
loaded: true
}
}
//some more code
}
});
})
You'll need to add support for the rest-spread-operator using babel.

Vue filter in v-for

Hi I am pretty new to VueJS and have started working on a very simple API request. I have an object that looks like this:
posts: [
text: "String",
choices: {"1":"Yes","2":"No"}
]
Coming from angular, this seems very straightforward. I would just use a filter to convert choices to an object and loop over it. However, I ran into a problem. When I attempt to use the filter 'log' or 'json' in the v-for, they don't work.
<template>
<div>
<li v-for="(post,index) in posts | log">
<ul>
{{ post.text | log }}
{{ post.choices | json }}
<li v-for="(value,key) in post.choices | json">
{{value}} {{key}}
</li>
</ul>
</li>
</div>
</template>
<script>
import {HTTP} from './main';
export default {
filters: {
json: function (value) {
return JSON.parse(value)
},
log: function (value) {
console.log(value)
}
},
props: [
'apiKey'
],
data: () => ({
posts: [],
post: [],
errors: []
}),
created() {
HTTP.get('questions', { headers: { 'Api-Key': this.apiKey} })
.then(response => {
this.posts = response.data
})
.catch(e => {
this.errors.push(e)
})
}
}
</script>
Then no data shows up, however they work fine in the mustache template. Any thoughts on how I can accomplish this?
tl;dr
works:
{{ post.choices | json }}
does not work:
<li v-for="(value,key) in post.choices | json">
Any work around? I can't use computed properties because this is a "sub-object" of an array and computed properties don't work that way, unless I am mistaken?
You cannot add a filter to a v-for directive.
Just parse the data before setting the posts data property:
HTTP.get('questions', { headers: { 'Api-Key': this.apiKey} })
.then(response => {
let posts = response.data;
post.forEach(p => p.choices = JSON.parse(p.choices));
this.posts = posts;
})
I don't see why you need to use JSON.parse, since the choices key refers to a valid JS object. Unless you have made a mistake, and that the choices key refers to a string.
Anyway, if you want to parse post.choice into a valid JS object, you can simply pass it into a custom function which returns the parsed JSON to v-for, for example:
<li v-for="(value,key) in postChoicesJson(post.choice)">
{{value}} {{key}}
</li>
And then declare a method for that:
postChoicesJson: function(obj) {
return JSON.parse(obj);
}
Note: Your DOM is invalid, because <li> elements must be a direct child of a <ul> or an <ol> element.

Updating custom component's form after getting a response

I'm trying to load in a Tutor's profile in a custom component with Laravel Spark. It updates with whatever I enter no problem, but the is always empty when loaded.
The component itself is as follows:
Vue.component('tutor-settings', {
data() {
return {
tutor: [],
updateTutorProfileForm: new SparkForm({
profile: ''
})
};
},
created() {
this.getTutor();
Bus.$on('updateTutor', function () {
this.updateTutorProfileForm.profile = this.tutor.profile;
});
},
mounted() {
this.updateTutorProfileForm.profile = this.tutor.profile;
},
methods: {
getTutor() {
this.$http.get('/tutor/current')
.then(response => {
Bus.$emit('updateTutor');
this.tutor = response.data;
});
},
updateTutorProfile() {
Spark.put('/tutor/update/profile', this.updateTutorProfileForm)
.then(() => {
// show sweet alert
swal({
type: 'success',
title: 'Success!',
text: 'Your tutor profile has been updated!',
timer: 2500,
showConfirmButton: false
});
});
},
}
});
Here's the inline-template I have:
<tutor-settings inline-template>
<div class="panel panel-default">
<div class="panel-heading">Tutor Profile</div>
<form class="form-horizontal" role="form">
<div class="panel-body">
<div class="form-group" :class="{'has-error': updateTutorProfileForm.errors.has('profile')}">
<div class="col-md-12">
<textarea class="form-control" rows="7" v-model="updateTutorProfileForm.profile" style="font-family: monospace;"></textarea>
<span class="help-block" v-show="updateTutorProfileForm.errors.has('profile')">
#{{ updateTutorProfileForm.errors.get('profile') }}
</span>
</div>
</div>
</div>
<div class="panel-footer">
<!-- Update Button -->
<button type="submit" class="btn btn-primary"
#click.prevent="updateTutorProfile"
:disabled="updateTutorProfileForm.busy">
Update
</button>
</div>
</form>
</div>
Very new to Vue and trying to learn on the go! Any help is much appreciated!
OK, firstly a bus should be used for communication between components, not within the components themselves, so updateTutor should be a method:
methods: {
getTutor() {
this.$http.get('/tutor/current')
.then(response => {
this.tutor = response.data;
this.updateTutor();
});
},
updateTutor() {
this.updateTutorProfileForm.profile = this.tutor.profile;
}
}
Now for a few other things to look out for:
Make sure you call your code in the order you want it to execute, because you appear to be emitting to the bus and then setting this.tutor but your function uses the value of this.tutor for the update of this.updateTutorProfileForm.profile so this.tutor = response.data; should come before trying to use the result.
You have a scope issue in your $on, so the this does not refer to Vue instance data but the function itself:
Bus.$on('updateTutor', function () {
// Here 'this' refers to the function itself not the Vue instance;
this.updateTutorProfileForm.profile = this.tutor.profile;
});
Use an arrow function instead:
Bus.$on('updateTutor', () => {
// Here 'this' refers to Vue instance;
this.updateTutorProfileForm.profile = this.tutor.profile;
});
Make sure you are not developing with the minified version of Vue from the CDN otherwise you will not get warnings in the console.
I can't see how you are defining your bus, but it should just be an empty Vue instance in the global scope:
var Bus = new Vue();
And finally, your mounted() hook is repeating the created() hook code, so it isn't needed. My guess is that you were just trying a few things out to get the update to fire, but you can usually do any initialising of data in the created() hook and you use the mounted hook when you need access to the this.$el. See https://v2.vuejs.org/v2/api/#Options-Lifecycle-Hooks