I'm creating a card carousel with a Spotify style thumbnail, (image as background, text on top). Content is stored in a Pinia store which I will be hooking up to Firebase eventually. I'm trying to set the image background but am getting this error
GET http://127.0.0.1:5173/%60$%7B%7Bcontent.image%7D%7D%60 404 (Not Found)
Here is my store code (condensed to the important bits)
export const useContentStore = defineStore('contentStore', {
state: () => {
return {
guides: [{
title: 'XX',
date: 'X',
description: "X",
image: './assets/images/content/thumbnail.png',
id: '1',
}]
}
}
})
Here is where I am trying to access that image path
<template>
<div class="card">
<img class="card-image" src="{{content.image}}"/>
<h1 class="title">{{content.title}}</h1>
<h2 class="subtitle"></h2>
</div>
</template>
<script setup>
/*
store
*/
import { useContentStore } from "#/stores/contentStore";
const contentStore = useContentStore();
/*
props
*/
const props = defineProps({
content: {
type: Object,
required: true,
},
});
</script>
And here is where the cards are being called
<template>
<div class="guides-container">
<h2 class="title">Guides</h2>
<div class="guides-list">
<GeneralCard
v-for="(content, index) in contentStore.guides"
:key="content.id"
:content="content"
/>
</div>
</div>
</template>
<script setup>
/*
imports
*/
import GeneralCard from "#/components/GeneralCard.vue";
/*
data
*/
const contentStore = useContentStore();
</script>
My gut instinct is that it's an issue with transferring the string through the store to the template, but I don't have any clue how to fix it. I've tried escaping the characters, using template literals on both the stored path and the image tag, played with URL() a bit, and I'm pretty sure it's not an issue with the actual path of the image (it works when I plug the path directly into the image tag)
Thanks for any help you can give!
The src attribute on the img is set improperly. It should be
<img class="card-image" :src="content.image"/>
Related
I have a popup component which renders a photo ID that works currently on one page and I am trying to also have the component visible on another page. When I add my component to another page it throws an error at me. Unsure what is causing the error. Stack suggests my cleanup function doesn't exist, it is a function written in the component.
If however I load the photoID popup from the page which already has the working component and then navigate to the second page and try to open the new component it will load fine until I refresh the page.
My Popup component
//popup.vue
<template>
<!--
Rendering of this component should be handled by the parent using a v-if.
Listen for event 'closeButtonPressed' to know when to stop rendering it.
-->
<div class="modal is-active">
<div class="modal-background" #click="$emit('closeButtonPressed')"></div>
<div class="modal-content">
<p class="image is-4by3">
<img class="photo-id-img" :src="photoIdObjectUrl">
</p>
</div>
<button class="modal-close is-large" aria-label="close" #click="$emit('closeButtonPressed')"></button>
</div>
</template>
<script>
import { mapState, mapActions } from 'vuex';
import utils from "../Utils/utils.js";
export default {
name: 'photoIdPopup',
props: {
clientId: {
type: Number,
required: true,
}
},
data: () => ({
/**
* This is a url we generate that points to a blob containing our photo
* id image. An img element's src attribute can be set to it to
* display the image.
*/
photoIdObjectUrl: null,
}),
destroyed() {
this.cleanUp();
},
methods: {
...mapActions({
getFile: "filestorage/getFile",
}),
async getPhotoIdObjectUrl() {
// Get photo id file from api.
const file = await this.getFile({
usercode: this.photoIdFile.usercode,
id: this.photoIdFile.id,
clientId: this.clientId,
});
// Convert the file to a blob.
const blob = new Blob([utils.str2bytes(file)]);
// Create a objectUrl from the blob for img element's src attribute.
return URL.createObjectURL(blob);
},
/** We have to cleanup the object urls we previously allocated. */
cleanUp() {
if (this.photoIdObjectUrl) {
URL.revokeObjectURL(this.photoIdObjectUrl);
this.photoIdObjectUrl = null;
}
},
async initializePhotoIdImage() {
this.photoIdObjectUrl = await this.getPhotoIdObjectUrl();
}
},
computed: {
...mapState({
photoIdFile(state) {
return state.filestorage['drLicense'];
}
}),
},
watch: {
photoIdFile: {
immediate: true,
handler(newPhotoIdFile) {
if (newPhotoIdFile) {
this.initializePhotoIdImage();
} else {
this.cleanup();
}
}
}
}
}
</script>
<style scoped>
.photo-id-img {
object-fit: contain;
}
</style>
How I'm calling the object on the new page, there is no difference in how I do it between this page and the page which has my working component.
//interview-summary.vue
<photoIdPopup
v-if="isPhotoIdShowing"
#closeButtonPressed="viewPhotoIdPressed"
:clientId="clientId"
/>
viewPhotoIdPressed() {
this.isPhotoIdShowing = !this.isPhotoIdShowing
Error Stack
Any help would be appreciated as I'm still learning Vue. My first assumption is that I'm missing an object in my state that I need to render the component properly but I'm not sure.
I am using markdown-it-vue to render Markdown into HTML on the fly :
<template>
<div ref="content">
<markdown-it-vue :content="content" />
<code>foobar</code>
</div>
</template>
When the props content is modified, the HTML content is injected at the <markdown-it-vue> component.
This HTML content contain some <code> tags and I want to replace them with something else. Unfortunately, I cannot select them with the following:
mounted() {
this.$refs.content.querySelectorAll('code').forEach(code => {
console.log(code)
})
},
Only the <code>foobar</code> is found, the ones in the <markdown-it-vue> are not found. However, they are in the DOM because if I do console.log(this.$refs.content) and I run querySelectorAll('code') on this variable from the Debug Console I get all my elements.
How can I access/replace the generated content from a sub-component?
I have tried to do it differently but with the same issues:
<template>
<div ref="content">
</div>
</template>
<script>
import Vue from 'vue'
import MarkdownItVue from 'markdown-it-vue'
let MyMarkdownItVue = Vue.extend(MarkdownItVue)
export default {
props: {
content: String,
},
mounted() {
let md = new MyMarkdownItVue({propsData: {content: this.content}});
md.$mount();
// Displays the full output with all my `<code>` tags
console.log(md.$el);
// But nothing is found there. I get an empty NodeList []
console.log(md.$el.querySelectorAll('code'))
}
};
</script>
I'm playing with Contentful! and I'm having trouble with Rich text content field.
I'm using '#contentful/rich-text-types' and #contentful/rich-text-html-renderer modules to customize the way this block is rendered and to display some assets and reference linked in Rich text content.
After calling getEntries in nuxt asyncData function, I've a description data available in my page component.
I'm using documentToHtmlString function with options.
Everything is working fine, but I would like to use a component I have already written (Post.vue), instead of returning the template in ES6 Template Strings.
I know that is possible, but I'm quite new to JS world.
I've tried to require components/post/Post.vue, but I don't know how to use it.
import { BLOCKS } from '#contentful/rich-text-types';
import { documentToHtmlString } from "#contentful/rich-text-html-renderer"
Vue component template where rich text field is rendered
<section class="container">
<div class="columns">
<div class="column">
<div v-html="formatContent(description)" />
</div>
</div>
</section>
I simply call formatContent method to call documentToHtmlString as follow (it works):
methods: {
formatContent(content) {
return documentToHtmlString(content, options)
}
}
And customize documentToHtmlString with options as described in doc:
const embeddedEntryRender = (node) => {
const { data: { target: entry} } = node
const fields = entry.fields
const sys = entry.sys
// LOOK HERE
// const postComponent = require('~/components/post/Post')
return `
<div class="column is-4">
<div class="card">
<div class="card-content">
<div class="media">
<div class="media-content">
<h3 class="title is-4">${fields.title}</h3>
<div class="subtitle is-6">${fields.description}</div>
</div>
</div>
<div class="content">
</div>
</div>
</div>
</div> `
}
const options = {
renderNode: {
[BLOCKS.EMBEDDED_ENTRY]: (node) => embeddedEntryRender(node),
// [BLOCKS.EMBEDDED_ASSET]: (node) => `<custom-component>${customComponentRenderer(node)}</custom-component>`
}
}
No errors detected
--
Thanks a lot
yep you can have a custom vue component in there with a different npm library, I had this same problem.
npm i contentful-rich-text-vue-renderer
in template:
<rich-text-renderer :document="document" :nodeRenderers="renderNode" />
where 'document' is the data sent form contentful, looks like your calling it description. RenderNode is a method described below.
in script:
data () {
return {
renderNode: [INLINES.ASSET_HYPERLINK]: (node, key, h) => {
return h('my-vue-component', { key: hey, props: { myProp: 'blah blah' }},'what I want inside the <my-vue-component> tag'`)
}
}
this might be kind of confusing. So First imprt the richTextRenderer component from that npm library and make sure to declare it in the components section of your vue component. (or gloablly)
Next pass into its 'document' prop the contentful rich text field
if you want custom rendering, pass into the nodeRenders prop a function (I had to declare it in the data section)
My example takes any asset hyperlink type and replaces it with a component of what I want inside the tag
I only got this to work if I globally declared the my-vue-component in the main.js file.
import MyVueComponent from 'wherever/it/is';
Vue.component('my-vue-component', MyVueComponent);
there are more configurations for this, just read the npm libs documentation (though its not great docs, it took my a long time to figure out how to pass props down, I had to read their github code to figure that out lol)
I believe this is a relatively unique problem, and as such I'm having difficulty trying to solve it.
I'm creating a file manager-like solution in Vue, and I'm looking for certain folders/files to display a unique thumbnail (in my example, showing the Creative Cloud logo if the 'Creative Cloud' folder is found). In my app I am using a component to represent each file.
The file-grid Vue file reads as such (sorry for the mess, I've been trying to integrate multiple different solutions to see what sticks):
<template>
<div id="localMain">
<div id="filesGrid">
<File :fileName="file"
:imageAddress="findImage($event)"
id="file"
v-for="file in files"
:key="file.id"></File>
</div>
</div>
</template>
<script>
import File from './LocalMain/File';
export default {
data() {
return {
creativeCloud: 'static/logos/creative-cloud.svg',
blankThumb: 'static/code.svg',
files: [
'Creative Cloud',
'Documents',
...
],
};
},
components: {
File,
},
methods: {
findImage: function findImage(e) {
/* Get the name of the file/folder, and choose a thumbnail accordingly */
const name = e.target.dataset.fileName;
let image = this.blankThumb;
if (name === 'Creative Cloud') {
image = this.creativeCloud;
} else {
image = this.blankThumb;
}
return image;
},
},
};
</script>
<style scoped>
/* styling */
</style>
The file component itself looks like this:
<template>
<div id="file">
<img :src="imageAddress" alt="Logo" id="fileImg" />
<h3 v-if="display">{{ fileName }}</h3>
</div>
</template>
<script>
export default {
data() {
return {
display: false,
};
},
props: {
fileName: String,
imageAddress: String,
},
};
</script>
<style scoped>
/* styling */
</style>
I apologise for the ambiguity in this question, but I'm quite confused.
I might be missing something, but why not just v-bind the method with the file name as the argument?
eg.
Parent template
<File :fileName="file"
:imageAddress="findImage(file)"
id="file"
v-for="file in files"
:key="file.id"></File>
Parent Javascript
findImage: function findImage(name) {
var image = this.blankThumb;
if (name === 'Creative Cloud') {
image = this.creativeCloud;
}
return image;
},
My Vue component contains some images. I want to do lazy-loading later, so I need to set the src of the images to a small image, first.
<template>
<div v-for="item in portfolioItems">
<a href="#{{ item.id }}">
<img
data-original="{{ item.img }}"
v-bind:src="/static/img/clear.gif"
class="lazy" alt="">
</a>
</div>
</template>
Gives me a bunch of errors, like:
[Vue warn]: Invalid expression. Generated function
body: /scope.static/scope.img/scope.clear.gif vue.common.js:1014[Vue
[Vue warn]: Error when evaluating expression "/static/img/clear.gif":
TypeError: Cannot read property 'call' of undefined (found in
component: )
webpack.config.js:
module.exports = {
// ...
build: {
assetsPublicPath: '/',
assetsSubDirectory: 'static'
}
}
This solution is for Vue-2 users:
In vue-2 if you don't like to keep your files in static folder (relevant info), or
In vue-2 & vue-cli-3 if you don't like to keep your files in public folder (static folder is renamed to public):
The simple solution is :)
<img src="#/assets/img/clear.gif" /> // just do this:
<img :src="require(`#/assets/img/clear.gif`)" // or do this:
<img :src="require(`#/assets/img/${imgURL}`)" // if pulling from: data() {return {imgURL: 'clear.gif'}}
If you like to keep your static images in static/assets/img or public/assets/img folder, then just do:
<img src="./assets/img/clear.gif" />
<img src="/assets/img/clear.gif" /> // in some case without dot ./
If you want to bind a string to the src attribute, you should wrap it on single quotes:
<img v-bind:src="'/static/img/clear.gif'">
<!-- or shorthand -->
<img :src="'/static/img/clear.gif'">
IMO you do not need to bind a string, you could use the simple way:
<img src="/static/img/clear.gif">
Check an example about the image preload here: http://codepen.io/pespantelis/pen/RWVZxL
This is how i solve it.:
items: [
{ title: 'Dashboard', icon: require('#/assets/icons/sidebar/dashboard.svg') },
{ title: 'Projects', icon: require('#/assets/icons/sidebar/projects.svg') },
{ title: 'Clients', icon: require('#/assets/icons/sidebar/clients.svg') },
],
And on the template part:
<img :src="item.icon" />
See it in action here
#Pantelis answer somehow steered me to a solution for a similar misunderstanding. A message board project I'm working on needs to show an optional image. I was having fits trying to get the src=imagefile to concatenate a fixed path and variable filename string until I saw the quirky use of "''" quotes :-)
<template id="symp-tmpl">
<div>
<div v-for="item in items" style="clear: both;">
<div v-if="(item.imagefile !== '[none]')">
<img v-bind:src="'/storage/userimages/' + item.imagefile">
</div>
sub: <span>#{{ item.subject }}</span>
<span v-if="(login == item.author)">[edit]</span>
<br>#{{ item.author }}
<br>msg: <span>#{{ item.message }}</span>
</div>
</div>
</template>
declare new variable that the value contain the path of image
const imgLink = require('../../assets/your-image.png')
then call the variable
export default {
name: 'onepage',
data(){
return{
img: imgLink,
}
}
}
bind that on html, this the example:
<img v-bind:src="img" alt="" class="logo">
hope it will help
You need use just simple code
<img alt="img" src="../assets/index.png" />
Do not forgot atribut alt in balise img
I had a similar issue with Vue where I tried to display several images by importing data from a configuration json file and then iterating over the data using v-for.
Even when I put require('../../assets/' + filename) right in the json, the images would never show. I eventually realized that Vue was interpreting my data value as a string, rather than a function. Good thing that javascript supports functions as a return type. So I made this function:
getImagePath(filename: string) {
return require('../../assets/' + filename);
}
I then just called that function from my v-for loop simply passing in the filenames from my config:
<v-list-item :key="place.id" v-for="place in placesOfPower">
<v-list-item-content class="justify-end">
<v-img :src="getImagePath(place.image)"
position="top center"
height="90"
width="30vw"/>
</v-list-item-content>
<v-list-item-content>
I found this thread on my search for a solution to show an image when it exists. I want to show a list of database entries that contain a type property. Each type should have a fitting png file in my vue assets folder. The whole list would break if a new type would be added without adding the image beforehand.
I found "Catch an error on requiring module in node.js" on stack overflow. The answer by Peter Lyons led me to my solution:
<template>
<v-data-table :items="items">
<template v-slot:item.type="{ item }">
<v-img
v-if="typeImageSource(item.type)"
:src="typeImageSource(item.type)"
/>
<template v-else>
{{ item.type }}
</template>
</template>
</v-data-table>
</template>
<script>
export default {
data () {
return {
// In reality this gets filled from a db:
items: [
{ id: 1, type: 'abc' },
{ id: 2, type: 'abcd' },
{ id: 3, type: 'efg' },
]
}
},
methods: {
typeImageSource: function (type) {
let src = ''
try {
src = require(`#/assets/types/${('' + type).toLowerCase()}.png`)
} catch (error) {
console.warn(`Image for type ${type} could not be found! Please add "${('' + type).toLowerCase()}.png" to the folder "#/assets/types/".\n\n`, error)
return null
}
return src
},
},
}
</script>
If you are using nuxt
use <img :src="'_nuxt/path_to_your_local_image'" />
if you are using vue
first use static src import : <img src="path_to_your_local_image" />
then inspect image element to see what src is rendered to the browser
then replace it with a dynamic src