Vue.js : concatenate path and than bind to v-img - vue.js

I am trying to concatenate 'assets' and to bind prod.productFileName to v-img :src
this works: <v-img :src="#/assets/abc.png" />
but I cannot do this: <v-img :src="#/assets/prod.productFileName" />
So I was trying to concatenate from the forEach bellow:
How can I do it?
<div v-for="prod in products" :key="prod.id">
<v-img :src="prod.productFileName" />
<script>
methods: {
getProducts() {
axios.get("https://localhost:44397/api/Product/List")
.then(res => {
this.products = res.data;
this.products.forEach(prod => {
prod.productFileName = `#/assets/${prod.productFileName}` // <--- I am getting an error
});
})
}
}

what you can do is store the response in this.products and no extra step is needed then in the img tag you can do is use template literals directly on the :src binding something like this
<div v-for="prod in products" :key="prod.id">
<v-img :src="`/assets/${prod.productFileName}`" />
<script>
methods: {
getProducts() {
axios.get("https://localhost:44397/api/Product/List")
.then(res => {
this.products = res.data;
})
}
}
and make sure that image is located in that location, otherwise the image will not be displayed

You have change your code like below.
this.products.forEach(prod => {
prod.productFileName = '/Uploads/' + prod.productFileName
});
Since this is a script you can simply append the string.

Related

AlpineJS async call to render array of objects

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.

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>

Change the item's icon dynamically according to the value received in response

I need to change the item's icon dynamically according to the value received in the server's response.
I'm trying to do this with a computed property, but I'm not getting the item_type value sent in the response.
I appreciate ideas on how to make this work?
<template>
<div class="q-pa-md">
<q-list>
<q-item
clickable
v-for="(item, index) in itens"
:key="item.id_item"
:id="index" >
<q-item-section avatar>
<q-icon :name="iconType" />
</q-item-section>
<q-item-section>
<q-item-label>{{ item.item_name }}</q-item-label>
</q-item-section>
</q-item>
</q-list>
</div>
</template>
<script>
export default {
data () {
return {
itens: [
// id_item:''
// item_name:'',
// item_type:''
]
}
},
computed : {
iconType(){
let type = this.itens.item_type; //does not work
if(type === 'link'){
return "link"
}else{
return 'double_arrow'
}
}
},
mounted: {
this.getItensList();
},
methods: {
getItensList(id){
this.$axios.get(`/itenslist`)
.then(response => {
if (response.data.success) {
this.itens = response.data.itens
} else {
}
})
.catch(error => {
});
},
}
}
</script>
You don't need a computed property, just this:
<q-icon :name="item.item_type === 'link' ? 'link' : 'double_arrow'" />
You can't use computed properties like this, instead you can create method
iconType(item){
let type = item.item_type
if(type === 'link'){
return "link"
} else {
return 'double_arrow'
}
}
and use it like <q-icon :name="iconType(item)" />
for cleaner code you may try this approach
iconType(item) {
return {
link: 'link',
otherType: 'otherIcon'
}[item.itemType] || 'double_arrow'
}
Try adding icon_type to every item after you fetch data - inside getItensList() method. You will have list with attached icon to every item.

How to update Image in vue

I'm a newbie in vue, I need help to update image, I'm using vform, in below onFileSelected function responsible creating and updating image, I'm successfully creating data with image, but now I'm stack in update the image, here is my code with form structure
Form
<form #submit.prevent="editMode ? update() : store()">
<div class="form-group">
<label for="image" class="font-weight-bold">Image</label>
<input class="form-control" type="file"
:class="{ 'is-invalid': form.errors.has('image') }"
name="image" id="image" accept="image/*"
#change="onFileSelected">
<has-error :form="form" field="image"></has-error>
</div>
</form>
I'm storing like this
onFileSelected(event) {
let file = event.target.files[0];
this.form.image = file;
},
store() {
this.$Progress.start();
this.form.busy = true;
this.form.post('/api/students', {
transformRequest: [function (data, headers) {
return objectToFormData(data)
}],
}).then(response => {
//......
}),
My edit code is
edit(student) {
this.editMode = true;
this.clearForm();
this.form.fill(student);
$('#modal').modal('show');
},
update() {
this.$Progress.start();
this.form.busy = true;
this.form.patch('/api/students/' + this.form.id)
.then(response => {
//.........
})
.catch(e => {})
},
I think your issue is that while your data is updating the render is not. For this you can put a key attribute (with some number or id) on the element that contains the image and then inside the update function once updated change the key like: forceReloadKey++ this will force the component to rerender and update.
Like so:
<form :key="reloadKey" #submit.prevent="editMode ? update() : store()">
...
update() {
...
this.reloadKey++
...

How to pass a variable and instantiate a new api request from my NavBar.vue component file to my News.vue views file?

I'm making an API request from https://newsapi.org/ and am able to do so with the created() method upon initiation. I have a component named Navbar.vue that includes buttons I'd like to use, upon click, to make a new api request and pass in a news source variable for the api request (e.g. 'cnn', 'fox-news'). Even though I've registered my News.vue in my Navbar.vue component, it doesn't appear I can use the created method to begin another instantiation. Here's a screen recording as well: https://drive.google.com/file/d/173x9PxLs5S2pWMYcHuXon0CQfoLwXNMT/view
I've tried calling NewsVue.created(source)
Top-Headlines/src/Components/Navbar.vue:
<template>
<div>
<b-navbar toggleable="lg" type="light" variant="success">
<b-container>
<b-navbar-brand href="#">Top Headlines</b-navbar-brand>
<b-navbar-toggle target="nav-collapse"></b-navbar-toggle>
<b-collapse id="nav-collapse" is-nav>
<b-button-group>
<b-button variant="danger" v-on:click="getNews('cnn')">CNN</b-button>
<b-button variant="info" v-on:click="getNews('espn')">ESPN</b-button>
<b-button variant="warning" v-on:click="getNews('nbc-news')">NBC News</b-button>
</b-button-group>
</b-collapse>
</b-container>
</b-navbar>
</div>
</template>
<script>
// import News from '../views/News';
import NewsVue from '../views/News.vue';
export default {
// components: {
// NewsVue,
// },
data() {
return {
meal: ''
}
},
methods: {
getNews(source) {
console.log(NewsVue);
NewsVue.created(source);
}
}
}
Top-Headlines/src/views/News.vue:
<template>
<div class="articles-container">
<template v-for="article in headlines">
<div :key="article.publishedAt" class="article-container">
<div class="article-source">
<a v-bind:href="article.url">
<h5>{{ article.title }}</h5>
</a>
</div>
</div>
</template>
</div>
</template>
<script>
// # is an alias to /src
"use strict";
export default {
name: "news",
data() {
return {
headlines: [],
search: "",
newsSource: ""
};
},
methods: {
getTopHeadlines(newsSource) {
console.log(newsSource);
let url = '';
if (newsSource !== "" && newsSource !== undefined) {
url =
"https://newsapi.org/v2/top-headlines?" +
"pageSize=10&" +
"sources="+newsSource+"&" +
"apiKey=ab07dee4fb7e4f198621ab4da0b1e5e9";
} else {
url =
"https://newsapi.org/v2/top-headlines?" +
"country=us&" +
"pageSize=10&" +
"apiKey=ab07dee4fb7e4f198621ab4da0b1e5e9";
}
var req = new Request(url);
fetch(req)
.then(response => response.json())
.then(json => {
this.headlines = json.articles;
});
}
},
created(newsSource) {
this.getTopHeadlines(newsSource);
}
};
</script>
I expect the page to reload with news source filtered headlines.
Error messages:
"TypeError: this.getTopHeadlines is not a function
at Object.created (webpack-internal:///./node_modules/cache-"
created is normaly called by the system and has this set to the component. It seems you are trying to call it directly. You can either set this yourself by using apply, or by simply passing it in.
EITHER WAY, DON'T NAME THE FUNCTION CREATED, as it is reserved for the Vue lifecycle.
NewsVue.created2(source, NewsVue);
To call a function created2 and set the this context.
NewsVue.created2.call(NewsVue, source);
// or
NewsVue.created2.apply(NewsVue, [source]);
Either way, the function created2 will be invoked with this set to NewsVue and 1 parameter source.
Use a watcher function, then set the data from the watcher.
BTW, NewsView should take newsSource as a property, and I don't even see that component in your template... Perhaps that's the root of your issue. You need something like <NewsView :newsSource='newsSource'/> in the template. Then move newsSource to props, and make the watcher immediate.
export default {
name: "news",
data() {
return {
headlines: [],
search: "",
newsSource: ""
};
},
watch: {
newsSource(value) {
const newsSource = value;
console.log(newsSource);
let url = '';
if (newsSource !== "" && newsSource !== undefined) {
url =
"https://newsapi.org/v2/top-headlines?" +
"pageSize=10&" +
"sources=" + newsSource + "&" +
"apiKey=ab07dee4fb7e4f198621ab4da0b1e5e9";
} else {
url =
"https://newsapi.org/v2/top-headlines?" +
"country=us&" +
"pageSize=10&" +
"apiKey=ab07dee4fb7e4f198621ab4da0b1e5e9";
}
var req = new Request(url);
fetch(req)
.then(response => response.json())
.then(json => {
this.headlines = json.articles;
});
}
},
};