Bind `id` to img src url using vue.js - javascript

I'm new to vue, and trying to follow a tutorial.
I'm trying to pull the image through that relates to the id of my article.
Everything else is coming through ok, article.id etc. The image will have the same filename as the article.id for the time being, so I just want to pull it into the component.
<div class="card bg-dark mb-2" v-for="article in articles" v-bind:key="article.id">
<div class="card-body">
<img class="card-img-top" v-bind:src="images/articles/`{{ $article.id }}`.jpg" width="100%" alt="Card image cap" />
<h5 class="card-title text-white">{{ article.id }}. {{ article.title }}</h5>
<h6 class="card-subtitle mb-2 text-white-50">{{ article.from }}</h6>
<p class="card-text text-truncate" style="max-width: 150px;">{{ article.description }}</p>
</div>
<div class="card-footer text-right">
<small class="text-white-50">Added by <span class="text-white">{{ article.added_by }}</span> ({{ article.created_at }})</small>
</div>
</div>
Please let me know if I need to provide anything else to this post.
Update:
I don't know if it makes a difference, but the article.id I'm using is being fetched from the Laravel API I've created.
The #app div is in in my index.blade.php file
export default {
data() {
return {
articles: [],
article: {
id: '',
title: '',
description: '',
from: '',
rating: '',
from: '',
created_at: '',
added_by: ''
},
recipe_id: '',
pagination: {},
edit: false
}
},
created() {
this.fetchArticles();
},
methods: {
fetchArticles(page_url) {
let vm = this;
page_url = page_url || '/api/articles'
fetch(page_url)
.then(res => res.json())
.then(res => {
this.articles = res.data;
vm.makePagination(res.meta, res.links);
})
.catch(err => console.log(err))
},
makePagination(meta, links) {
let pagination = {
current_page : meta.current_page,
last_page : meta.last_page,
next_page_url : links.next,
prev_page_url : links.prev
};
this.pagination = pagination;
}
}

A bound attribute is parsed as javascript. So if you want the string images/articles/123.jpg for article 123 you need to pass it to the :src attribute like so:
<img :src="`images/articles/${article.id}.jpg`" />
or
<img :src="'images/articles/' + article.id + '.jpg'" />

Mustaches cannot be used inside HTML attributes. Instead, use a v-bind directive (v-bind:src or :src)
You can bind the image src like this:
<img :src="'images/articles/' + article.id + '.jpg'" />
You can see this working on the screenshot of the fiddle I developed for your answer
Check out the Fiddle :)

Related

VUEJS/X Data Template loads before my computed

first of all I'm sorry for my bad English, I'm using a translator.
I'm a beginner with VUE and VUE X, there are surely big mistakes.
I have a problem with VUE, currently I am trying to display a publication thanks to its ID.
Here is my DATA :
data(){
return {
list: [this.$store.dispatch('allPublications')],
id:'',
feed: '',
}
},
Here is my STORE action:
publicationId:({commit}, messages) => {
instance.get('/publications/' + messages.id)
.then(function(response){
commit('setMessage', response.data.publication)
console.log(response)
this.feed = response.data.publication.data
})
.catch(function(error){
console.log(error)
})
},
Here is my computed:
computed: {
...mapState({
user: 'profileUser',
publication: 'publicationFeed',
message: 'publicationInfos'
}),
message(){
return this.$store.state.message;
},
},
Here is my state :
setMessage: function(state, message){
state.message = message
},
Here is my template :
<template>
<div class="card-body" #click="publicationId(message.id)">
<span class="badge bg-secondary">{{ message.User.username }}</span>
<div class="dropdown-divider"></div>
<div class="card-text d-flex justify-content-between align-items-md-center">
<p class="card-text d-flex flex-start">{{ message.message }}</p>
</div>
<span class="message__date">{{ message.createdAt.split('T')[0]}}</span>
</div>
<img class="card-img-top" alt="..." :src="message.image">
Some screenshot to help you more :
VUE google chrome tool
Before reloading the page, everything is OK
After reloading the page, everything is going wrong
Everything works fine as long as I don't reload the page, as soon as I reload it I get an error, my computed value disappears and I can't get the data from my computed.
I have searched a lot of information without being able to solve this problem, thanks to you for the help!
It seems that i've solved this issue doing this :
data() {
return {
componentLoaded: false,
list: [this.$store.dispatch('allPublications')],
id: '',
feed: '',
}
},
In the mounted :
mounted() {
this.componentLoaded = true;
this.id = this.$route.params.id;
this.$store.dispatch('publicationId', { id: this.$route.params.id })
},
In the computed :
computed: {
message() {
if (!this.componentLoaded) {
return null
} else {
return this.$store.state.message;
}
},
},
And I add a v-if in the template
<template>
<div class="verification" v-if="componentLoaded === true">
<div class="card-body" #click="publicationId(message.id)">
<span class="badge bg-secondary">{{ message.User.username }}</span>
<div class="dropdown-divider"></div>
<div class="card-text d-flex justify-content-between align-items-md-center">
<p class="card-text d-flex flex-start">{{ message.message }}</p>
</div>
<span class="message__date">{{ message.createdAt.split('T')[0] }}</span>
</div>
<img class="card-img-top" alt="..." :src="message.image" />
</div>
<div v-else>
<h1>test</h1>
</div>
It worked for me, I hope it will help for those who needs.

Vuejs emit not working form child to parent

I'm working on this app and the idea is to show details of the cars in a sidebar on click. There are several issues like the sidebar is showing four times and I resolve it somehow but I don't know why is it showing four times. now I don't getting any response on emit call help me out please, I try $parent.$emit, $root.$emit but not seems working!!!
<template>
<div class="home">
<!-- warehouse details -->
<div
v-for="(detail, detailindex) in details"
:key="detailindex"
class="container mt-5 mb-5"
>
<h1>
{{ detail.name }}
<span class="location">{{ detail.cars.location }}</span>
</h1>
<!-- vehicle details -->
<SingleGarage :detail="detail"> </SingleGarage>
</div>
<b-sidebar
id="my-sidebar"
title="Sidebar with backdrop"
backdrop-variant="dark"
ref="mySidebar"
backdrop
shadow
#emitData="testingEmit()"
>
<div class="px-3 py-2">
<h1>{{currentCar}}</h1>
</div>
</b-sidebar>
</div>
</template>
<script>
// # is an alias to /src
import axios from "axios";
import SingleGarage from "../components/SingleGarage";
export default {
components: { SingleGarage },
name: "Home",
data: () => ({
details: String,
currentCar: 'String',
}),
methods:{
testingEmit(data){
this.currentCar = data
console.log('data from emit',data)
}
},
mounted() {
axios
.get("https://api.jsonbin.io/b/5ebe673947a2266b1478d892")
.then((response) => {
var results;
response.data.forEach((element) => {
element.cars.vehicles.sort((a, b) => {
a = new Date(a.date_added);
b = new Date(b.date_added);
results = a > b ? -1 : a < b ? 1 : 0;
return results * -1;
});
});
this.details = response.data;
});
},
};
</script>
<template>
<div class="vGrid mt-4">
<div
class="gridItem border vehicle singleCar"
v-for="(vehicle, vehicleIndex) in detail.cars.vehicles"
:class="'griditem' + vehicleIndex"
:key="vehicle._id"
>
<SingleCar
:vehicle="vehicle"
#click.native="testingTef(vehicleIndex)"
></SingleCar>
</div>
</div>
</template>
<script>
import SingleCar from "#/components/SingleCar";
export default {
name: "SingleGarage",
components: { SingleCar },
props: ["detail"],
data: () => ({
dummyImg: require("#/assets/img/dummycar.png"),
currentCar : 1
}),
methods: {
testingTef(vehicleIndex) {
this.$parent.$emit('emitData',this.detail.cars.vehicles[vehicleIndex].make)
this.$root.$emit('bv::toggle::collapse', 'my-sidebar')
console.log(this.detail.cars.vehicles[vehicleIndex].make)
console.log(this.detail.cars.vehicles[vehicleIndex].date_added)
this.currentCar = this.detail.cars.vehicles[vehicleIndex].make;
},
},
};
</script>
<template>
<div class="singleCar">
<!-- conditionally show image -->
<img
class="carImg"
:src="vehicle.img"
v-if="vehicle.img"
alt="No Preview"
/>
<img class="carImg" :src="dummyImg" v-else alt="No Preview" />
<div class="p-3">
<h3 class="make">{{ vehicle.make }}</h3>
<div class="modelDetails">
<div class="model d-flex ">
<p class="bold">Model:</p>
<p class="price ml-auto ">{{ vehicle.model }}</p>
</div>
<div class="price d-flex ">
<p class="bold">Price:</p>
<p class="price ml-auto ">€{{ vehicle.price }}</p>
</div>
</div>
<p class="dateAdded ml-auto ">{{ vehicle.date_added }}</p>
</div>
</div>
</template>
<script>
export default {
name: "SingleCar",
props: ["vehicle"],
data: () => ({
dummyImg: require("#/assets/img/dummycar.png"),
}),
methods:{
working(){
console.log('working');
console.log(this.vehicle.make)
}
}
};
</script>
Thanks for your help.
So a few things you can try to fix this
in your Home.vue you can change
#emitData="testingEmit()"
to
#emitData="testingEmit"
// or
#emitData="testingEmit($event)"
You are telling to the function testingEmit that is not params to parse. So you need to take out the () and Vue will parse everything that comes from the $event or you cant say put the $event as a param in your testingEmit (second option).
For your SingleGarage.vue you can take the $parent.$emit and replace it with
this.$emit('emitData',this.detail.cars.vehicles[vehicleIndex].make)

str.split() is splitting at every letter and not at the delimiter

I'm trying to split a string of tags that I'm getting back from firebase by ',' and then store them in my data() for rendering. When I render out the firebase snapshot data after I split they tags in the console they are correctly formatted like so:
"tag1" "tag2"
but when I render them in my vue app, they are split at every letter (or at least a div is generated and is displaying for every letter) like so:
<div>"t"</div>
<div>"a"</div>
<div>"g"</div>
<div>"1"</div>
Can someone let me know if this is an issue with my vue app or my firebase call?
I have included some dummy data that I'm using to show the final result of how the tags array in data should look.
Here's my app for reference :)
<div id="container">
<div class="topbar">
<h3 id="header">DevDeep</h3>
<div id="searchDiv">
<b-form-input id="search" v-model="search" placeholder="search articles"></b-form-input>
<font-awesome-icon id="searchBtn" #click="searchResults()" icon="search" />
</div>
</div>
<!--main part of page-->
<div class="bod">
<div class="sideContainer">
<h4 id="category">categories</h4>
<ul id="listoflinks" v-for="cat in categories" :key="cat.toString()">
<div #click="searchResultsByCat(cat)">{{cat}}</div>
</ul>
</div>
<div id="centerContainer">
<div>
<h5> Tag list </h5>
<div class="flexContainer">
<div id="selectedTags" v-for="tag in tagList" :key="tag.toString()">
<span id="tag" #click="removeTag(tag)">{{tag}}</span>
</div>
<font-awesome-icon id="searchBtn" #click="searchbyTags()" icon="search" />
</div>
</div>
<div id="artDiv" v-for="art in articles" :key="art.title">
<div #click="gotoArticle(art)" id="thumbnail">
<h5 >{{art.title}}</h5>
<img :src=art.image height="100px" width="100px" alt="article thumbnail">
</div>
<!--TAGS-->/////////////////////////////////////////
<div class="flexContainer">
<div id="tags" v-for="tag in art.tags" :key="tag.toString()">
<span id="tag" #click="addTagToSearch(tag)">{{tag}}</span>
</div>
</div>
<!--TAGS-->//////////////////////////////////////////
</div>
</div>
<div class="addContainer">adds</div>
</div>
<!--main part of page-->
</div>
</template>
<script>
const fb = require('../../fireconfig.js')
export default {
name: 'Home',
data:function() {
return{
articles: [
{
title: 'modern web app security',
body: 'some content here about web app security',
image: 'dd',
tags: ['cyber security','web apps', 'web development']
},
{
title: 'intro to ArcGIS',
body: 'an article to show users how to do GIS stuff',
image: 'dwwd',
tags: ['arcgis','node js','gps services']
},
{
title: 'Vue.js injecting props',
body: 'this is how to inject vue props into a component',
image: 'dwwd',
tags: ['vue','props','components','web development','web apps']
}
],
categories:['web development', 'arcgis','cyber security','partnerships'],
search: '',
tagList: []
}
},
props: {
post: Object
},
created(){
console.log('db post will go here later')
let ref = fb.db.collection('articles')
ref.get()
.then(snapshot => {
if (snapshot.empty) {
console.log('No matching documents.');
return;
}
snapshot.forEach(doc => { //this works for each doc
console.log(doc.id, '=>', doc.data());
doc.data().tags = doc.data().tags.split(",") // its splitting each letter we need to only split at the comma
console.log(doc.data().tags)
this.articles.push(doc.data()) //push object into state array
})
})
.catch(err => {
console.log('Error getting documents', err);
});
},
}
</script>
doc.data() will produce a new object each time you call the method.
doc.data().tags = doc.data().tags.split(",") // its splitting each letter we need to only split at the comma
The above line is pointless you create a new object, but never store it in a variable. Which means that you can't use it later. Instead assign the resulting object to variable and use that.
const data = doc.data(); // create the data object only once
data.tags = data.tags.split(",");
this.articles.push(data);
Since the split of the tags didn't persist in the question code. tag in:
<div id="tags" v-for="tag in art.tags" :key="tag.toString()">
Is set to a character, because art.tags is a string and not an array.

Vue.js: show profile image in laravel public folder

I am creating commenting system.
And I integrated vue.js in my laravel project.
In my comment area, I want to show user profile image in my laravel
public folder.
But I have no idea how to show image.
What I want to achieve is,
if user has a profile image, I want to show it in comment area. But if
user doesn't have a profile image, I want to show an avatar.
I used vue avatar component so I think I want to use it.
My profile images are stored in public/uploads/profile.
comment.vue
<template>
<div>
<div class="reply-comment" >
<div class="user-comment">
<div class="user">
<!---<img src="{{ $comment->user->img }}" class="image-preview__image">--->
<avatar :username="comment.user.name" :size="45"></avatar>
</div>
<div class="user-name">
<span class="comment-name"><a :href=" '/user/' + 'profile' +'/'+ comment.user.id + '/' ">{{ comment.user.name }}</a></span>
<p>{{ comment.body }}</p>
</div>
</div>
</div>
<div class="reply">
<button #click="addingReply = !addingReply" class="reply-button" :class="{ 'red' : !addingReply, 'black' : addingReply }">
{{ addingReply ? 'Cancel' : 'Add Reply'}}
</button>
</div>
<div class="user-reply-area" v-if="addingReply">
<div class="reply-comment">
<input v-model='body' type="text">
</div>
<button #click="addReply" class="comment-button"><span>Add Reply</span></button>
</div>
<replies ref='replies' :comment="comment"></replies>
</div>
</template>
<script>
import Avatar from 'vue-avatar'
import Replies from './replies.vue'
export default {
components: {
Avatar,
Replies
},
data() {
return {
body: '',
addingReply: false
}
},
props: {
comment: {
required: true,
default: () => ({})
},
post: {
required: true,
default: () => ({})
}
},
methods: {
addReply() {
if(! this.body) return
axios.post(`/comments/${this.post.id}`, {
comment_id: this.comment.id,
body: this.body
}).then(({data})=> {
this.body = ''
this.addingReply = false
this.$refs.replies.addReply(data)
})
.catch(function (error) {
console.log(error.response);
});
}
}
}
</script>
profile.php
public function user(){
return $this->belongsTo(User::class, 'user_id');
}
user.php
public function profile(){
return $this->hasOne(Profile::class);
}
public function comments()
{
return $this->hasMany(Comment::class);
}
comment.php
protected $with = ['user'];
protected $appends = ['repliesCount'];
web.php
Route::get('results/{post}/comments', 'CommentController#index');
If your database column name for the image file is img then
you can do it like below:
<div class="user" v-if="comment.user.img">
<img :src="'uploads/profile/'+comment.user.img">
</div>
<div class="user" else>
<img :src="default_image_path_here">
</div>
If you are using multiple images for a user you need to add a loop as follow:
<div class="user" v-if="comment.user.img.length > 0">
<span v-for="(item, index) in comment.user.img">
<img :src="'uploads/profile/'+item.your_image_name_column">
</span>
</div>
<div class="user" else>
<img :src="default_image_path_here">
</div>

Vue.js show white space (line breaks)

How would I show line space in vue.js. Right now everything is after each other....
Already tried this:
https://laracasts.com/discuss/channels/vue/vuejs-how-to-return-a-string-with-line-break-from-database
But nothing seems work. Trying this for 3 days now -_-.
I'm using Vue.js 1.0 and browserify.
Thanks a lot!
--EDIT--
<template>
<div>
<bar :title="title" />
<div class="Row Center">
<div class="Message Center" v-if="!loading">
<div class="Message__body" v-if="messages">
<div class="Message__item__body" v-for="message in messages" v-link="{ name: 'Message', params: { message: message.slug }}">
<div class="Message__item__body_content">
<p class="Message__title">{{ message.subject }}</p>
</div>
<div class="Message__item__body_content">
<p>Reacties: {{ message.totalReactions }}</p>
</div>
<div class="Message__item__body_content">
<p>Door: {{ message.user.name }} {{ message.user.last_name }}</p>
</div>
</div>
<pagination :last-page="lastPage" :page="page" :name="Message" />
<p v-if="noMessages" class="Collection__none">Er zijn momenteel geen berichten voor het topic {{ topic.name }}.</p>
</div>
</div>
<div class="Loader" v-if="loading">
<grid-loader :loading="loading" :color="color" :size="size" />
</div>
</div>
<div class="Row center" v-if="!loading && page == 1 && topic">
<div>
<button type="submit" class="Btn Btn-main" v-link="{ name: 'NewMessage', params: { topic: topic.slug }}">Nieuw bericht</button>
</div>
</div>
</div>
</template>
<script>
import Bar from '../Shared/Bar.vue';
import Pagination from '../Shared/Pagination.vue';
import Topic from '../../Services/Topic/TopicService';
import { GridLoader } from 'vue-spinner/dist/vue-spinner.min.js';
export default {
components: { Bar, Pagination, GridLoader },
data () {
return {
title: 'Berichten',
messages: [],
topic: null,
noMessages: false,
loading: false,
color: "#002e5b",
page: 1,
lastPage: 1,
}
},
route: {
data ({ to }) {
this.loading = true;
this.page = to.query.page || 1;
Topic.show(this.$route.params.topic, this.page)
.then((data) => {
this.topic = data.data.topic;
if(!data.data.messages.data.length == 0) {
this.messages = data.data.messages.data;
this.lastPage = data.data.messages.last_page;
} else {
this.noMessages = true;
}
this.loading = false;
});
}
}
}
</script>
When I do it like this:
<div class="Message__body__message">
<p>{{ message.message.split("\n"); }}</p>
</div>
It only adds comma's.
--EDIT--
Set container white-space style to pre-line, as in:
<div style="white-space: pre-line;">{{textWithLineBreaks}}</div>
When you split the message, you get multiple data items, which you should handle with a v-for.
But also see LMK's answer wherein you don't have to split the message.
new Vue({
el: '#app',
data: {
message: `this is a message
it is broken across
several lines
it looks like a poem`
}
});
<script src="//cdnjs.cloudflare.com/ajax/libs/vue/2.0.3/vue.min.js"></script>
<div id="app">
<template v-for="line in message.split('\n')">{{line}}<br></template>
</div>
You have to transform your data before rendering it with Vue.
const lines = stringWithLineBreaks.split('\n')
// then render the lines
I can give a more specific answer if you share the code you're working with.

Categories