Why when i pass source manually it works but when i tried to pass source with vuex it doesn't - Vue3 / Vuex / Composition API [duplicate] - vue.js

I have a case where in my Vue.js with webpack web app, I need to display dynamic images. I want to show img where file name of images are stored in a variable. That variable is a computed property which is returning a Vuex store variable, which is being populated asynchronously on beforeMount.
<div class="col-lg-2" v-for="pic in pics">
<img v-bind:src="'../assets/' + pic + '.png'" v-bind:alt="pic">
</div>
However it works perfectly when I just do:
<img src="../assets/dog.png" alt="dog">
My case is similar to this fiddle, but here it works with img URL, but in mine with actual file paths, it does not work.
What should be correct way to do it?

I got this working by following code
getImgUrl(pet) {
var images = require.context('../assets/', false, /\.png$/)
return images('./' + pet + ".png")
}
and in HTML:
<div class="col-lg-2" v-for="pic in pics">
<img :src="getImgUrl(pic)" v-bind:alt="pic">
</div>
But not sure why my earlier approach did not work.

Here is a shorthand that webpack will use so you don't have to use require.context.
HTML:
<div class="col-lg-2" v-for="pic in pics">
<img :src="getImgUrl(pic)" v-bind:alt="pic">
</div>
Vue Method:
getImgUrl(pic) {
return require('../assets/'+pic)
}
And I find that the first 2 paragraphs in here explain why this works? well.
Please note that it's a good idea to put your pet pictures inside a subdirectory, instead of lobbing it in with all your other image assets. Like so: ./assets/pets/

You can try the require function. like this:
<img :src="require(`#/xxx/${name}.png`)" alt class="icon" />
The # symbol points to the src directory.
source: Vue URL transfrom rules

There is another way of doing it by adding your image files to public folder instead of assets and access those as static images.
<img :src="'/img/' + pic + '.png'" v-bind:alt="pic" >
This is where you need to put your static images:

Your best bet is to just use a simple method to build the correct string for the image at the given index:
methods: {
getPic(index) {
return '../assets/' + this.pics[index] + '.png';
}
}
then do the following inside your v-for:
<div class="col-lg-2" v-for="(pic, index) in pics">
<img :src="getPic(index)" v-bind:alt="pic">
</div>
Here's the JSFiddle (obviously the images don't show, so I've put the image src next to the image):
https://jsfiddle.net/q2rzssxr/

Vue.js uses vue-loader, a loader for WebPack which is set up to rewrite/convert paths at compile time, in order to allow you to not worry about static paths that would differ between deployments (local, dev, one hosting platform or the other), by allowing you to use relative local filesystem paths. It also adds other benefits like asset caching and versioning (you can probably see this by checking the actual src URL being generated).
So having a src that would normally be handled by vue-loader/WebPack set to a dynamic expression, evaluated at runtime, will circumvent this mechanism and the dynamic URL generated will be invalid in the context of the actual deployment (unless it's fully qualified, that's an exception).
If instead, you would use a require function call in the dynamic expression, vue-loader/WebPack will see it and apply the usual magic.
For example, this wouldn't work:
<img alt="Logo" :src="logo" />
computed: {
logo() {
return this.colorMode === 'dark'
? './assets/logo-dark.png'
: './assets/logo-white.png';
}
}
While this would work:
<img alt="Logo" :src="logo" />
computed: {
logo() {
return this.colorMode === 'dark'
? require('./assets/logo-dark.png')
: require('./assets/logo-white.png');
}
}
I just found out about this myself. Took me an hour but... you live, you learn, right? 😊

I also hit this problem and it seems that both most upvoted answers work but there is a tiny problem, webpack throws an error into browser console (Error: Cannot find module './undefined' at webpackContextResolve) which is not very nice.
So I've solved it a bit differently. The whole problem with variable inside require statement is that require statement is executed during bundling and variable inside that statement appears only during app execution in browser. So webpack sees required image as undefined either way, as during compilation that variable doesn't exist.
What I did is place random image into require statement and hiding that image in css, so nobody sees it.
// template
<img class="user-image-svg" :class="[this.hidden? 'hidden' : '']" :src="userAvatar" alt />
//js
data() {
return {
userAvatar: require('#/assets/avatar1.svg'),
hidden: true
}
}
//css
.hidden {display: none}
Image comes as part of information from database via Vuex and is mapped to component as a computed
computed: {
user() {
return this.$store.state.auth.user;
}
}
So once this information is available I swap initial image to the real one
watch: {
user(userData) {
this.userAvatar = require(`#/assets/${userData.avatar}`);
this.hidden = false;
}
}

Here is Very simple answer. :D
<div class="col-lg-2" v-for="pic in pics">
<img :src="`../assets/${pic}.png`" :alt="pic">
</div>

<img src="../assets/graph_selected.svg"/>
The static path is resolved by Webpack as a module dependency through loader.
But for dynamic path you need to use require to resolve the path. You can then switch between images using a boolean variable & ternary expression.
<img :src="this.graph ? require( `../assets/graph_selected.svg`)
: require( `../assets/graph_unselected.svg`) " alt="">
And of course toggle the value of the boolean through some event handler.

<div
v-for="(data, i) in statistics"
:key="i"
class="d-flex align-items-center"
>
<img :src="require('#/assets/images/'+ data.title + '.svg')" />
<div class="ml-2 flex-column d-flex">
<h4 class="text-left mb-0">{{ data.count }}</h4>
<small class="text-muted text-left mt-0">{{ data.title }}</small>
</div>
</div>

You can use try catch block to help with not found images
getProductImage(id) {
var images = require.context('#/assets/', false, /\.jpg$/)
let productImage = ''
try {
productImage = images(`./product${id}.jpg`)
} catch (error) {
productImage = images(`./no_image.jpg`)
}
return productImage
},

I also faced this problem.
Try it:
computed {
getImage () {
return require(`../assets/images/${imageName}.jpg`) // the module request
}
}
Here is a good article that clarifies this:
https://blog.lichter.io/posts/dynamic-images-vue-nuxt/

Tried all of the answers here but what worked for me on Vue2 is like this.
<div class="col-lg-2" v-for="pic in pics">
<img :src="require(`../assets/${pic.imagePath}.png`)" :alt="pic.picName">
</div>

As I am using Gridsome, this way worked for me.
**I also used toLowerCase() method
<img
:src="
require(`#/assets/images/flags/${tournamentData.address.country_name.toLowerCase()}.svg`)
"
/>

well the best and easiest way that worked for me was this of which i was fetching data from an API..
methods: {
getPic(index) {
return this.data_response.user_Image_path + index;
}
}
the getPic method takes one parameter which is the name of the file and it returns the absolute path of the file maybe from your server with the file name simple...
here is an example of a component where i used this:
<template>
<div class="view-post">
<div class="container">
<div class="form-group">
<label for=""></label>
<input type="text" class="form-control" name="" id="" aria-describedby="helpId" placeholder="search here">
<small id="helpId" class="form-text user-search text-muted">search for a user here</small>
</div>
<table class="table table-striped ">
<thead>
<tr>
<th>name</th>
<th>email</th>
<th>age</th>
<th>photo</th>
</tr>
</thead>
<tbody>
<tr v-bind:key="user_data_get.id" v-for="user_data_get in data_response.data">
<td scope="row">{{ user_data_get.username }}</td>
<td>{{ user_data_get.email }}</td>
<td>{{ user_data_get.userage }}</td>
<td><img :src="getPic(user_data_get.image)" clas="img_resize" style="height:50px;width:50px;"/></td>
</tr>
</tbody>
</table>
</div>
</div>
</template>
<script>
import axios from 'axios';
export default {
name: 'view',
components: {
},
props:["url"],
data() {
return {
data_response:"",
image_path:"",
}
},
methods: {
getPic(index) {
return this.data_response.user_Image_path + index;
}
},
created() {
const res_data = axios({
method: 'post',
url: this.url.link+"/view",
headers:{
'Authorization': this.url.headers.Authorization,
'content-type':this.url.headers.type,
}
})
.then((response)=> {
//handle success
this.data_response = response.data;
this.image_path = this.data_response.user_Image_path;
console.log(this.data_response.data)
})
.catch(function (response) {
//handle error
console.log(response);
});
},
}
</script>
<style scoped>
</style>

I encountered the same problem.
This worked for me by changing '../assets/' to './assets/'.
<img v-bind:src="'./assets/' + pic + '.png'" v-bind:alt="pic">

The image needs to be transcribed.
What worked for me is putting the images in public folder. i.e public/assets/img
Dynamic Image Tag:
<div v-for="datum in data">
<img
class="package_image"
style="max-width:200px;"
alt="Vue logo"
:src="`./assets/img/${datum.image}`"
>
<div>

I have a solution you may want to try.
Define a method like below
methods: {
getFlagImage(flag){
return new URL('/resources/img/flags/'+flag+'.png', import.meta.url);
},
}
then images can be called with the established for loop
<li :class=" 'nav-item', {'active': language === key} " v-for="(value,
key) in locals" :key="value ">
<a class="dropdown-item" #click="switchLanguageTo(key)">
<img :src="getFlagImage(key)" /> {{value}}
</a>
</li>

I think I found the best solution to this problem by accident!
The only thing you have to do is to start addressing from the root.
Doesn't work
<img :src="'../assets/' + pic + '.png">
Work:
<img :src="'src/assets/' + pic + '.png">

As of today, working with VUE 3 + Typescript & composition API, what I have done is wrap require function in try catch to handle crash.
computed: {
getImage() {
let imgSrc = "";
try {
imgSrc = require(`../assets/weather-widget-icons/ww-icon-${this.weatherForecast.main.toLowerCase()}.svg`);
} catch (error) {
console.error(`Image '../assets/weather-widget-icons/ww-icon-${this.weatherForecast.main.toLowerCase()}.svg' not found!`);
}
return imgSrc;
}
}
and call this function in image tag:
<div class="weather-icon">
<img :src="getImage" :alt="weatherForecast.main" />
</div>

Related

How to mark notification as Read using Vue js?

I'm trying to mark a notification as read when a user clicks on it. Right now, when a user clicks on one of the notifications, it marks all of the user's notifications as read, instead of just the one.. I created a "click" function on the <a>.
AppHeader.vue:
<li class="dropdown-item preview-item dropdown-item-notifications" v-for="(unreadNotification, index) in unreadNotifications" :key="index">
<a class="dropdown-item" type="button" #click="markAsRead()">
<div class="preview-item-content flex-grow py-2">
<div v-if="unreadNotification ? unreadNotification.type === 'App\\Notifications\\InterviewRequestCandidateReply' : ''">
<p class="font-weight-light small-text"> <InterviewRequestCandidateReplyMessage /> </p>
</div>
<div v-else-if="unreadNotification ? unreadNotification.type === 'App\\Notifications\\InterviewRequestReplyConfirmed' : ''">
<p class="font-weight-light small-text"> <InterviewRequestReplyConfirmedMessage /> </p>
</div>
</div>
</a>
</li>
methods: {
markAsRead: async function() {
try {
const response = await employerService.markNotificationAsRead();
this.loadNotificationsData();
console.log(this.notifications);
} catch (error) {
this.$toast.error("Some error occurred, please refresh!");
}
},
},
employerService.js from code above:
export function markNotificationAsRead(id) {
return http().post(`employer/notifications/${id}`);
}
In my #click="markAsRead() function I think I need to get the id so maybe something like this #click="markAsRead(unreadNotification.id). Now the tricky part and where I'm stuck is, how can I pass this id into the markNotificationAsRead() function below?
const response = await employerService.markNotificationAsRead();
I'm not sure how to do this. I'm using Laravel for my backend.
--------------------- UPDATE: ---------------------
Something strange is happening. I know that the answers provided should work, but for some reason it's still marking all records as read.
EmployerNotificationsController.php:
public function markAsRead($id)
{
$notifications = Auth::user()->notifications->where('id', $id)->first()->markAsRead();
return response()->json($notifications, 200);
}
api.php:
Route::post('/employer/notifications/{id}', 'EmployerNotificationsController#markAsRead')
->name('employer.notifications.mark-as-read');
Any ideas why?
You're absolutely right about passing the ID to the click handler: #click="markAsRead(unreadNotification.id)
Your markAsRead method will receive the ID as an argument that you can then pass to your service method:
markAsRead: async function(id) {
try {
const response = await employerService.markNotificationAsRead(id);
//...
},
<li class="dropdown-item preview-item dropdown-item-notifications" v-for="(unreadNotification, index) in unreadNotifications" :key="index">
<a class="dropdown-item" type="button" #click="markAsRead(unreadNotification.id)">
<div class="preview-item-content flex-grow py-2">
<div v-if="unreadNotification ? unreadNotification.type === 'App\\Notifications\\InterviewRequestCandidateReply' : ''">
<p class="font-weight-light small-text"> <InterviewRequestCandidateReplyMessage /> </p>
</div>
<div v-else-if="unreadNotification ? unreadNotification.type === 'App\\Notifications\\InterviewRequestReplyConfirmed' : ''">
<p class="font-weight-light small-text"> <InterviewRequestReplyConfirmedMessage /> </p>
</div>
</div>
</a>
</li>
methods: {
markAsRead: async function(id) {
try {
const response = await employerService.markNotificationAsRead(id);
this.loadNotificationsData();
console.log(this.notifications);
} catch (error) {
this.$toast.error("Some error occurred, please refresh!");
}
},
},
just like this ...

Vue.js - data access with dynamic components

I have simple web page that shows a result list and users can switch between table or li style.
I got simple Vue with two dynamic components : results-array and results-list.
It works like a charm except when I switch from the first component to the second one: I loose results properties called in mustache (I got blank values with no error) :
{{contact.img_path}} does not show anything
whereas
<img :src="contact.img_path" /> works great
** UPDATE **
Here a simplified jsfiddle to try out: https://jsfiddle.net/eywraw8t/151906/
My files :
contact.js
var list = Vue.component('results-list', {
template: '#results-list-template',
props: ['display_mode', 'contacts']
});
var table = Vue.component('results-array', {
template: '#results-array-template',
props: ['display_mode', 'contacts'],
});
const app = new Vue({
el: '#app',
router,
data: {
currentResultsView: 'results-array',
display_mode: 'array',
contacts: [],
contact: { company: {}, phones: {}, section: {}, schedule_type: {}}, // Declaring Reactive Properties
phone: {} // Declaring Reactive Properties
},
created () {
this.navigate(this.$route.query.page);
},
methods: {
changeDisplay: function(event, type){
this.currentResultsView = (type == "array") ? "results-array" : "results-list";
this.display_mode = type;
console.log('contacts', this.contacts[0].lastname) // WORKS !
},
navigate: function(page){
var page = page > 0 ? page : 1;
axios.get('/', {params: {page: page, fulltext_search: this.fulltext_search, sort_dir: this.sort_dir}})
.then((response) => {
this.contacts = response.data.entries;
});
}
}
});
index.html
<ul>
<li #click="changeDisplay($event, 'hcard')" :class="{active:display_mode == 'hcard'}">Carte de visite</li>
<li #click="changeDisplay($event, 'vcard')" :class="{active:display_mode == 'vcard'}">Vignette verticale</li>
<li #click="changeDisplay($event, 'array')" :class="{active:display_mode == 'array'}">Tableau</li>
</ul>
<div id="app">
<script type="text-x-template" id="results-array-template">
<table>
<tr>
<th></th>
<th>Firstname</th>
<th>Lastname</th>
</tr>
<tr v-for="contact in contacts">
<td><img :src="contact.img_path" class="contact_img" /></td>
<td>{{ contact.firstname }}</td>
<td>{{ contact.lastname }}</td>
</tr>
</table>
</script>
<script type="text-x-template" id="results-list-template">
<ul>
<li v-for="contact in contacts">
{{contact.firstname}} <!-- **Does not show anything** -->
<img :src="contact.img_path" /> <!-- **Works great!** -->
</li>
</ul>
</script>
<div id="results" :class="display_mode" class="clearfix">
<keep-alive>
<component v-bind:is="currentResultsView" :display_options="display_options" :display_mode="display_mode" :contacts="contacts" ></component>
</keep-alive>
</div>
</div>
You should either remove the key contact from the data part of your Vue root instance, or use another name in the v-for iterator (e.g. v-for="myContact in contacts")
UPDATE
Also, you should not use script tags for the template - use template instead, because Chrome ignores non-JavaScript script tags.
The solution - https://codepen.io/TMCDOS/pen/gjYWNY
The solution is to move the two template scripts outside of the #app div

v-model on a newly created property?

On created I:
created: function () {
_.forEach(this.users, (user) => {
Vue.set(user, 'published', true);
});
}
In my template I have:
<div v-for="user in this.users">
<input type="checkbox" v-model="user.published">
</div>
The issue is the checkbox does not seem to be bound to the newly set published property. Why is this and how can I fix it?
Please note, I am unable to change how I am adding the published property to each user.
Try it that way:
<div v-for="user in users">
<input type="checkbox" v-model="user.published">
<button #click="user.published=!user.published">Toggle</button>
</div>
In your hook:
created() {
for (i in this.users) {
this.users[i].published = true;
}
}

Unable to fetch records using axios in Laravel 5.5

I'm trying to fetch records using axios.
But problem is that I'm getting the error in the console which is written below:
GET http://127.0.0.1:8000/getData 500 (Internal Server Error)
Here is my component in which I'm trying to get the data
<template lang="html">
<div class="getData">
<h1 class="text-center text-muted">Recently Added Chemicals</h1>
<div class="table-responsive">
<table class="table table-striped">
<tr>
<th>Chemical Name</th>
<th>Status</th>
<th>Stock</th>
</tr>
<tr v-for="list in lists">
<td>{{list.chem_name}}</td>
<td>
<p v-if="lists.is_active = 1" style="color: green;">Active</p>
<p v-else style="color: red;">Not Active</p>
</td>
<td>
<p v-if="lists.is_stocked = 1" style="color: green;">In Stock</p>
<p v-else style="color: red;">Out Of Stock</p>
</td>
</tr>
</table>
</div>
</div>
</template>
<script>
export default {
data () {
return {
lists: {},
errors: {}
}
},
mounted(){
axios.get('getData').then((response) => this.lists = response.data).catch((error) => console.log(this.error));
}
}
</script>
Then my getData function in resource controller
public function getData(){
$id = Auth::user()->id;
$data = chemType::where('user_id', $id)->orderBy('id', 'DESC')->get();
return response()->json(200, $data);
}
Please help!!!
500 error means error on backend, look at chrome dev tools network to look at the exception thrown.
I think the error is in response()->json(). The first parameter should be $data
Confirm you have the following line in your bootstrap.js file
window.axios.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest';
And also check that you have the following in the header of your html page.
<meta name="csrf-token" content="{{ csrf_token() }}">
I did it by doing changes in my resource controller. What was wrong in the controller is that , I was not sending the json response after creating the new record. So one thing I did sent the json response.
On vue part , I used $emit function from my parent component to child and in child I refreshed the record using that $emit function and yes, I am now getting newly created records without any page reloading.
Please Check that Your URL is Working or Not. Means it is fetching data in Controller or Not. It is giving some response or Not. I have faced the same problem but My URL was not fetching any data because My Model was not included. SO after including the model, my problem was solved.

Passing a variable to a Vue component from the parent page that called it

I have a simple table that displays all of my data:
main file.php
<table class="table table-bordered table-hover" id="all-jobs">
<thead>
<tr>
<th>{{ __('Job Name') }}</th>
<th>{{ __('Job Description') }}</th>
<th>{{ __('Job Status') }}</th>
<th>{{ __('Job Applications') }}</th>
<th>{{ __('Manage') }}</th>
</tr>
<tr>
<td></td>
<td></td>
<td></td>
<td class="non_searchable"></td>
<td class="non_searchable"></td>
</tr>
</thead>
</table>
<div id="app">
<div id="editJob" class="modal fade in" role="dialog">
<div class="modal-dialog">
<!-- Modal content-->
<div class="modal-content">
<edit-job id=""></edit-job>
</div>
</div>
</div>
</div>
Now, I have a edit button that I am trying to open an edit modal for that specific row:
<a href='' data-id='{$job->id}' class='btn btn-xs btn-danger' data-toggle='modal' data-target='#editJob'><i class='fa fa-close'></i></a>";
The href is is location in one of the of my data table, I am trying to pass that to my .vue file so I can use it for my get and post requests:
myfile.vue
<template>
<div>
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal">×</button>
<h4 class="modal-title">Edit Job</h4>
</div>
<div class="modal-body">
<form method="post" #submit.prevent="signIn" #keydown="errors.clear($event.target.name)">
<!-- Removed code, it's just inputs !-->
</form>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-info btn-fill btn-wd" v-on:click="addJob">Save</button>
<button type="button" class="btn btn-default" data-dismiss="modal">Close</button>
</div>
</div>
</template>
<script>
export default
{
props: ['id'],
data: function ()
{
return {
countries: [],
name: '',
summary: '',
salarytype: '',
salaryfrom: '',
salaryto: '',
location: '',
contactemail: '',
contactphone: '',
errors: new Errors()
}
},
methods:
{
addJob: function()
{
axios.post('/jobs/edit', this.$data)
.then(response => {
if(response.data.status === true){
$('#editJob').modal('hide');
getJobTable();
}
else{
formError = response.data.message;
}
})
.catch(error => this.errors.record(error.data))
}
},
mounted: function()
{
console.log($(this).data('id'));
axios.get('/jobs/my-job/')
.then(response => {
this.name = response.data.name
this.summary = response.data.summary
this.salarytype = response.data.salary_type
this.salaryfrom = response.data.salary_from
this.salaryto = response.data.salary_to
this.location = response.data.location
this.contactemail = response.data.contact
this.contactphone = response.data.phone
})
axios.get('/countries')
.then(response => {
this.countries = response.data;
})
}
}
</script>
How can I past my href id to my to use for my request? Thanks
MY structure:
Created-jobs.blade.php
https://pastebin.com/TPBnC1qP
Edit-Job.vue
https://pastebin.com/30UWR5Nn
app.js
https://pastebin.com/1yxZWvVC
The table just populates the data, and adds the dropdown like so:
<ul class='icons-list'>
<li class='dropdown'>
<a href='#' class='dropdown-toggle' data-toggle='dropdown' aria-expanded='false'>
<i class='icon-menu9'></i>
</a>
<ul class='dropdown-menu dropdown-menu-right'>
<li>
<a data-id='{$job->id}' onclick='getID({$job->id})' data-toggle='modal' data-target='#editJob'>
<i class='icon-file-pdf'></i> Edit Job
</a>
</li>
<li>
<a href='javascript:void();' data-id='{$job->id}' onclick='deleteJob({$job->id})'>
<i class='icon-cross'></i> Delete Job
</a>
</li>
</ul>
</li>
</ul>
You don't give a lot of information about the structure of your application, but it looks like you are using at least one single file component to display the data inside your modal which is being entirely displayed via Bootstrap. It also looks like the table with the id values you want to pass to Vue is outside of the Vue itself.
That being the case, the way you can pass the data you want to the single file component is to capture the Vue in a variable and then set the id whenever the link in your table is clicked.
Let's suppose your main.js or app.js looks like this:
import Vue from 'vue'
import EditJob from './EditJob.vue'
Vue.component('edit-job', EditJob)
const app = new Vue({
el: '#app',
data:{
id: null
}
})
// Add a click handler for the links with the `data-id` property.
// This is using jQuery (because you are using Bootstrap) but you
// can do this any way you want.
$("[data-id]").on("click", function(){
// Set the Vue's data to the data-id property in the link.
app.id = this.dataset.id
})
Notice how the code captures the result of new Vue(...) in the variable app. Then I've added the data property, id to the Vue and a click handler for all of your links that sets app.id to this.dataset.id whenever a link is clicked. In this way, every time a link is clicked, the data property in the Vue will be set to the id of the clicked link.
Then, all you need to do is bind the id property to your component.
<edit-job :id="id"></edit-job>
and your EditJob component will always get the updated id.
Here is a working example.
Edit
In the code you added to your example, you are defining all of your jQuery script in Created-jobs.blade.php. That being the case, the function you wrote, getID doesn't have access to the app variable you defined in your webpack bundle because of normal javascript scoping rules. To make app accessible to your jQuery code, add it to the window.
window.app = new Vue({
el: '#app',
data:{
id: null
}
});
Secondly, though you defined the getID function, nothing calls it. It needs to be called when the links are clicked. Add this somewhere to your jQuery code in Created-jobs.blade.php (ideally in a document ready function).
$("[data-id]").on("click", getID)