Bootstrap image asset not processed for props style attribute on Vue Component (Nuxt.js) - vue.js

I have been trying to import an image asset relative path to a banner component. The following works just fine.
<b-img src="~/static/images/carousel1.jpg" alt="Samyojya Consultants banner"/>
On html, I see it rendered as this
<div class="card-body"><img src="/_nuxt/static/images/carousel1.jpg"...
But the v-bind style representation like this does not bundle the image
<b-img :src="imgSrc" :alt="title+'banner'"/>
I can see on the html that imgSrc value is passing on but not compiled by asset processor
<div class="card-body"><img src="~/static/images/carousel1.jpg" ...
Is there a way we can explicitly trigger this compilation? require doesn't seem to work too.
<b-img :src="require(imgSrc)" :alt="require(title)+'banner'"/>
This dynamic style is needed for my use-case.

Create a computed prop (or method, or similar) to resolve (require) the relative path:
export default {
data() {
return {
title: 'Image title'
}
},
computed: {
imgSrc() {
// Relative to component directory
return require('./image.png')
}
}
}
And then reference that in your template:
<b-img :src="imgSrc" :alt="title+' banner'"/>

On the calling (parent) template, I used this
<banner :imgSrc="imgSrc" ...
And the data export in parent like this.
export default {
data: function(){
return {
imgSrc:require('../static/images/carousel2.jpg')
}
},
...
In the child component where the banner is drawn.
<b-img :src="imgSrc"...
Note: require needs a relative path (../static) from components/pages while without require we can use absolute (~/static).

<b-img :src="require('../static/images/carousel1.jpg')" alt="Samyojya Consultants banner"/>

Related

How to change the language of whole application from the dropdown in Navbar component in vue

I created a language selection dropdown in my Navbar component. So here is my navbar component:
<div>
<h6>{{ translate("welcomeMsg")}} </h6>
<select name="lang" v-model="lang">
<option value="en">English</option>
<option value="de">Deutsch</option>
</select>
</div>
<script>
export default {
mixins: [en, de],
data() {
return {
lang: "en",
};
},
methods: {
translate(prop) {
return this[this.lang][prop];
}
}
}
</script>
So the parent of this component is an Index.vue which is main component in my application.
<div id="app">
<Topnav/>
<Navbar/>
<router-view></router-view>
<Footer/>
</div>
Currently, I am able to change the language in my Navbar component. So according to the selected value in the dropdown in Navbar component, welcomeMsg is changing. What I am trying to do is I want to put this pieve of code to TopBar "{{ translate("welcomeMsg")}} ", and according to the value of the dropdown in Navbar component, I want to change this value.
Can you help me with this or can you give me an idea how to do it?
If I understand you correctly, you want to use translate method inside Topnav component.
This method is however defined in Navbar, so it's not accessible in Topnav.
To use it elsewhere you could create a mixin with this method to import it to any component. I don't recommend this solution though as mixins are making the code messy.
Another solution is to create a component with translate method defined inside. Let this component do just that: translate a message passed by prop and render it inside some div:
<div>
{{ translatedMessage }}
</div>
<script>
mixins: [en, de],
props: {
message: {
type: String,
default: ''
},
language: {
type: String,
default: 'en'
}
},
computed: {
translatedMessage() {
return this[this.language][this.message];
}
}
</script>
You can reuse this component anywhere in the application. You would still need to pass a language prop somehow, possibly the solution would be to use vuex store to do this, since language is probably global for entire application.
For easier and more robust solutions I would use vue-i18n, which #Abregre has already suggested in his comment: https://stackoverflow.com/a/70694821/9463070
If you want a quick solution for a full-scale application and you don't have a problem with dependencies, you could try to use vue-i18n.
It's a plugin for vue that does exactly this for multi-locale websites/apps in Vue.
https://www.npmjs.com/package/vue-i18n
EDIT
Then in order to use it globally in your app, you should use vuex.
Keep the language selection state there and then wherever you want to use it, you make a computed function with the state.language getter.
The translate function should be a global registered filter
https://v2.vuejs.org/v2/guide/filters.html

<b-form-file> does not keep the filename visible when switched to other components

I am new to Vue and using Bootstrap view for my form.
I am using b-form-file for uploading a file in component say Component2.vue and I am able to browse my local filesystem and select the file. The filename is seen in the b-form-file after the selection.
When I visit another component(Component1.vue) via the UI and come back to Component2.vue, the filename is not visible in the browse anymore. Although I have used v-model to bind it to the file and the bind is happening but the filename is not displayed in the b-form-file.
I want to keep the filename visible in the b-form-file as it is binded to 'file' throughout the session.
I am using b-form-file like this:
<form>
<b-form-file
id="form-model"
v-model="file"
required
placeholder="Choose a model..."
></b-form-file>
<form>
<script>
export default {
data() {
return { file: null }
}
}
</script>
well I was using tab view to display <b-form-file> and encountered the same behavior. As I was not using <router-view> so the <keep-alive> solution did not worked for me. I found a nice little trick by dynamically setting the placeholder prop of the <b-form-file> component to display the filename. The following code is pretty self-explanatory I think.
<template>
<b-form-file
v-model="file"
id="file"
:placeholder="fileName"
drop-placeholder="Drop file here..."
accept=".xlsx, application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
/>
</template>
<script>
import {
BFormFile,
} from "bootstrap-vue";
export default {
components: {
BFormFile,
},
data() {
return {
file: null,
fileName: "Choose a .xlsx file or drop it here...",
};
},
watch: {
file(newVal, _) {
this.fileName = newVal.name;
},
},
};
</script>

Nuxt render function for a string of HTML that contains Vue components

I'm trying to solve this for Nuxt
Codesandbox of a WIP not working: https://codesandbox.io/s/zw26v3940m
OK, so I have WordPress as a CMS, and it's outputting a bunch of HTML. A sample of the HTML looks like this:
'<h2>A heading tag</h2>
<site-banner image="{}" id="123">Slot text here</site-banner>
<p>some text</p>'
Notice that it contains a Vue component <site-banner> that has some props on it (the image prop is a JSON object I left out for brevity). That component is registered globally.
I have a component that we wrote, called <wp-content> that works great in Vue, but doesn't work in Nuxt. Note the two render functions, one is for Vue the other is for Nuxt (obviously this is for examples sake, I wouldn't use both).
export default {
props: {
html: {
type: String,
default: ""
}
},
render(h, context) {
// Worked great in Vue
return h({ template: this.html })
}
render(createElement, context) {
// Kind of works in Nuxt, but doesn't render Vue components at all
return createElement("div", { domProps: { innerHTML: this.html } })
}
}
So the last render function works in Nuxt except it won't actually render the Vue components in this.html, it just puts them on the page as HTML.
So how do I do this in Nuxt? I want to take a string of HTML from the server, and render it on the page, and turn any registered Vue components into proper full-blown Vue components. Basically a little "VueifyThis(html)" factory.
This was what worked and was the cleanest, thanks to Jonas Galvez from the Nuxt team via oTechie.
export default {
props: {
html: {
type: String,
default: ""
}
},
render(h) {
return h({
template: `<div>${this.html}</div>`
});
}
};
Then in your nuxt.config.js file:
build: {
extend(config, ctx) {
// Include the compiler version of Vue so that <component-name> works
config.resolve.alias["vue$"] = "vue/dist/vue.esm.js"
}
}
And if you use the v-html directive to render the html?
like:
<div v-html="html"></div>
I think it will do the job.
Here's a solution on codesandbox: https://codesandbox.io/s/wpcontent-j43sp
The main point is to wrap the dynamic component in a <div> (so an HTML tag) in the dynamicComponent() template, as it can only have one root element, and as it comes from Wordpress the source string itself can have any number of top level elements.
And the WpContent component had to be imported.
This is how I did it with Nuxt 3 :
<script setup lang="ts">
import { h } from 'vue';
const props = defineProps<{
class: string;
HTML: string
}>();
const VNode = () => h('div', { class: props.class, innerHTML: props.HTML })
</script>
<template>
<VNode />
</template>
There was not need to update nuxt.config.ts.
Hopefully it will help some of you.
I made some changes to your codesandbox. seems work now https://codesandbox.io/s/q9wl8ry6q9
Things I changed that didn't work:
template can only has one single root element in current version of Vue
v-bind only accept variables but you pass in a string.

Dynamic rendering of images with v-for?

I want to render images from a local folder with v-for, but how to make it 100% dynamic?
I tried the solutions offered in this thread. When I tried the most useful solution, I just get a blank page, unless I fill the array with the name of the images.
<template>
<div class="comp__cardroster">
<div class="container__cards" >
<div v-for="image in images" :key="image" class="tile--outer">
<img class="tile--inner" :src="selectImage(image)" :alt="image"></div>
</div>
</div>
</template>
<script>
export default {
data() {
return {
images: []
}
}
methods: {
selectImage(image) {
return require('#/assets/card-images/' + image + ".jpg")
}
}
}
</script>
The code above gives me a blank page. But when I fill the array with values, like below, I do get a result. But I don't want this obviously.
data() {
return {
images: [1,2,3,4,5,6,7,8,9,10]
}
}
I would want the code to render the images dynamically, no matter how many images I do have in my "assets/card-images" folder and without having to manually add values in the array each time I add a new image to the folder.
What am I doing wrong? I thank you for any advise.
UPDATE
Things I tried;
moving the "images" array from data to computed
moving "selectImage" method from methods to computed
moving both "images" array and "selectImage" method into computed
Either I get a blank page, or I get the same result as before
I don't think I was clear enough with my comments, so I'll explain what I meant with an example; you should be able to apply this to your use case with minimal effort. My src file structure and a screenshot of the result are also at the bottom of this answer.
The template I've made is basically the same as yours (without the extra divs with classes):
<template>
<div>
<div v-for="image in images" :key="image">
<img :src="selectImage(image)" :alt="image" />
</div>
</div>
</template>
Here's my script. I'll go through it all below:
<script>
export default {
name: 'app',
computed: {
images: function() {
const x = require.context('#/assets/card-images/', true, /\.png$/)
return this.importAll(x)
}
},
methods: {
importAll(r) {
return r.keys().map(x =>
x.substring(2, x.length) // remove "./" from file names
)
},
selectImage(image) {
return require('#/assets/card-images/' + image)
}
}
}
</script>
computed
The computed section is where you define your dynamically generated or computed values. Since you want your images to be dynamically generated, I've made images a computed function (can probably just be a value, you can play around with that).
All that images does is it uses require.context to get a list of all of the .png images in my #/assets/card-images/ folder, and trims the first couple of characters from them.
methods
importAll just retrieves and trims the image names. I've done this because otherwise, it'll think the images are at #/assets/card-images/./xxxxx.png - there's probably a better way of doing this but it works well enough.
selectImage gets an image from the file name you pass in (if it exists). If the image name doesn't exist, this will break but that shouldn't happen with how this is implemented.
Note: You can technically shorten the v-for loop by putting it directly on the img tag if you really want to, though I'd argue this is less readable:
<template>
<div>
<img v-for="image in images"
:key="image"
:src="selectImage(image)"
:alt="image" />
</div>
</template>
Here is my src folder structure. It doesn't matter what the images are called, as long as they have the same extension as you're using in your script tag:
Here is what the code prints out (all of the images are just copies of the Vue logo):
EDIT
If you want to keep your initial images array, you can move the computed stuff into the lifecycle method mounted or created (depending on your use-case). Read more about lifecycle methods here or here. Here's what my component would look like with the calculations in mounted:
<template>
<div>
<div v-for="image in images" :key="image">
<img :src="selectImage(image)" :alt="image" />
</div>
</div>
</template>
<script>
export default {
name: 'app',
data() {
return {
images: []
}
},
mounted() {
const x = require.context('#/assets/card-images/', true, /\.png$/)
this.images = this.importAll(x)
},
methods: {
importAll(r) {
return r.keys().map(x =>
x.substring(2, x.length) // remove "./" from file names
)
},
selectImage(image) {
return require('#/assets/card-images/' + image)
}
}
}
</script>
Use a requireAll method to get an array, and your count. Or a custom loader.
How to load all files in a directory using webpack without require statements
node.js require all files in a folder?

Can't load images from relative path with Vue V-Lazy-Image Plugin

I'm using this plugin to create a portfolio gallery on a single page components application but I'm stuck loading the images from a relative path, it works fine if I use a full URL path.
When I use the full URL the image Loads and loose the blur as expected, instead when I use a relative path the image load but it keeps blurred and it doesn't load the class .v-lazy-image-loaded into the image tag. Any Ideas? here is the code.
LazyImage.vue
<template>
<v-lazy-image :src="src" />
</template>
<script>
import VLazyImage from 'v-lazy-image'
export default {
props: {
// HERE I TRIED TO USE A FUNCTION ALSO WITH NO LUCK
// src: Function
src: String
},
components: {
VLazyImage
},
// UPDATE: I added a method to test here.
methods: {
consoleSuccess (msg) {
console.log(msg)
}
}
}
</script>
Portfolio.vue
<template>
<section id="portfolio">
// I'M TRYING THIS TWO OPTIONS
// UPDATED: I Added here the events calls and its just calling the intersect but the load.
<v-lazy-image src="../assets/img/innovation.jpg" alt="alternate text" #load="consoleSuccess('LOADED!')" #intersect="consoleSuccess('INTERSECS!')"></v-lazy-image> <-- this just work with full url ex. http://cdn...
<v-lazy-image :src="require('../assets/img/innovation.jpg')" alt="alternate text"></v-lazy-image> <-- this loads the image but remains blured
</section>
</template>
<script>
import VLazyImage from '#/components/lazyImage'
export default {
components: {
VLazyImage
}
}
</script>
UPDATE
I added a method to test and realized that just the #intersect event it's called and the #load won't fire at all with relative paths.
I noticed this was caused by bug in the component code:
if (this.$el.src === this.src) {
should be replaced by
if (this.$el.getAttribute('src') === this.src) {
I've sent pull request for this.