Haven't touched Vue in a while, now I'm building a <HeroImage> component in Vue and Vuetify which takes an src attributes, put the image, and combine it a certain way with the slot and other props.
Usually, the answer is wrapping the src string in a request(). But now it throws this message at compile time:
Critical dependency: the request of a dependency is an expression
Here's what I'm trying to do:
<!--
src/views/Home.vue
-->
<template>
<!-- This does not work -->
<HeroImage src="#/assets/hero-image.png" />
<!-- As comparison, this somehow works -->
<img :src="require('#/assets/hero-image.png')" />
</template>
<script>
import HeroImage from "#/components/HeroImage"
export default {
components: { HeroImage }
}
</script>
<!--
src/components/HeroImage.vue
-->
<template>
<img alt="Hero Image" class="align-center justify-center" :src="require(src)" />
</template>
<script>
export default {
name: "HeroImage",
props: {
src: String
}
}
</script>
Related
I'm implementing an application with Vue Js and I've the following code:
<template>
<simple-page title="list-patient" folder="Patient" page="List Patient" :loading="loading">
<list-patients #patientsLoaded="onPatientsLoaded"/>
</simple-page>
</template>
Both simple-page and list-patients are custom components created by me. Inside ListPatients I've an HTTP request on Create callback, as follows:
created() {
axios.get("...").then(response => {
...
this.$emit('patientsLoaded');
})
},
Then, my objective is to handle the patientsLoaded event and uptade the loading prop on the top parent component, as follows:
data() {
return {
loading: true
}
},
methods: {
onPatientsLoaded(params) {
this.loading = false;
}
}
However, the created method is not being triggered inside the list-patients component. The only way I can make this work is by removing :loading.
Any one can help?
Edit 1
Code of simple page:
<template>
<section :id="id">
<!-- Breadcrumb-->
<breadcumb :page="page" :folder="folder"/>
<!-- Breadcrumb-->
<!-- Simple Card-->
<simple-card :title="page" :icon="icon" :loading="loading" v-slot:body>
<slot>
</slot>
</simple-card>
<!-- Simple Card-->
</section>
</template>
Code of simple card:
<b-card>
<!-- Page body-->
<slot name="body" v-if="!loading">
</slot>
<!--Is loading-->
<div class="loading-container text-center d-block">
<div v-if="loading" class="spinner sm spinner-primary"></div>
</div>
</b-card>
Your list-patients component goes in the slot with name "body". That slot has a v-if directive so basically it is not rendered and hooks are not reachable as well. Maybe changing v-if to v-show will somehow help you in that situation. Anyway, you have deeply nested slots and it is making things messy. I usually declare loading variable inside of the component, where fetching data will be rendered.
For example:
data () {
return {
loading: true;
};
},
mounted() {
axios.get('url')
.then(res => {
this.loading = false;
})
}
and in your template:
<div v-if="!loading">
<p>{{fetchedData}}</p>
</div>
<loading-spinner v-else></loading-spinner>
idk maybe that's not best practise solution
v-slot for named slots can be indicated in template tag only
I suppose you wished to place passed default slot as body slot to simple-card component? If so you should indicate v-slot not in simple-card itself but in a content you passed it it.
<simple-card :title="page" :icon="icon" :loading="loading">
<template v-slot:body>
<slot>
</slot>
</template>
</simple-card>
I'm trying to use slots to inject content from a parent component to its child, but Vue keeps rendering the default content, not parsing the content sent from its parent.
This is the code of the parent component, which in turn is a child of a global component:
let parentComponent = {
template: `
<div>
<child-component>
<template v-slot:action>Close</template>
<template v-slot:element>Modal</template>
</child-component>
</div>
`,
components: {
'child-component': childComponent
}
};
And here is its child component, where I want to pass content:
let childComponent = {
template: `
<button>
<slot name="action">Open</slot>
<slot name="element">Window</slot>
</button>
`,
};
The button is still displaying the default content: "Open Window"
What am I doing wrong?
EDIT:
This is the rest of the content, just in case it helps:
index.html:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>VueJS</title>
</head>
<body>
<div id="app">
<vue-directives></vue-directives>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue#2.5.16/dist/vue.js"></script>
<script src="components/slotDirective/slotDirectiveSubcomponent.js"></script>
<script src="components/slotDirective/slotDirective.js"></script>
<script src="components/VueDirectives.js"></script>
<script>
let vue = new Vue({
el: '#app'
});
</script>
</body>
</html>
And VueDirectives.js:
Vue.component('vue-directives', {
template: `
<div>
<h3>{{ title }}</h3>
<parentComponent/>
</div>
`,
data() {
return {
title: "VueJS directives",
}
},
components: {
parentComponent
}
});
I fixed the error. I don't know the reason, but it wasn't working either if I loaded Vue.js from a CDN, or if I manually downloaded it locally, as a single file.
Then I finally tried installing it from npm, and loading Vue.js in node_modules/vue/dist/vue.js, and this way it works. I assume otherwise Vue.js does not comply with all its functionality.
I'm not sure that slots are designed for this. If you are just changing the text of a button passing props into the parentComponent is how i would go about it. E.g.
<parentComponent buttonText="some text or bind with a data value or computed prop"/>
see: https://v2.vuejs.org/v2/guide/components-props.html
Using slots... If you are experimenting try this, a reusable dialog box that you can pop in anywhere and control the content. E.g.
// myDialog
<v-dialog>
<slot>Here you can put what you want</slot>
</v-dialog>
And to use:
<myDialog>
<template>
<myContent /> Or just put content here without another component
</template>
</myDialog>
I've created a component, and in that component is an audio tag. I've been trying to pass the file path of the mp3 to the component from its parent, but the audio element doesn't seem to be able to load the file successfully for some reason. The element just ends up greyed out, and I don't get any kind of error. I know the properties are being passed successfully because I'm also passing in the title of the track, and that loads just fine in the component. However, if I hardcode the same path in for the source, then it works fine. Which isn't a big deal, but I have 11 tracks to do and it would be much easier with a v-for statement.
Here's my parent component:
<template>
<div container>
<div class=banner>
<img src="../assets/CryptoLogo2.svg"/>
</div>
<div class="albumcontainer">
<div class="covercontainer"><img src="../assets/TIADCover(Final).png"/></div>
<div class="arrow"></div>
<div class="tracklistcontainer">
<table class="tracklist">
<tr class="track" v-for="track in tracks" :key="track.file" style="padding: 20px;">
<td>
<player :name="track.name" :file="track.file" />
</td>
</tr>
</table>
</div>
</div>
<div class="bio">
<p>
</p>
</div>
<div class="footer">
<div class="footericons">
<img src="../assets/icons/Facebook.svg" />
<img src="../assets/icons/Instagram.svg" />
<img src="../assets/icons/Twitter.svg" />
</div>
</div>
</div>
</template>
<script>
import player from './player'
export default {
name: "Album1",
data() {
return {
tracks: [
{
name: '5G',
file: '../assets/tracks/5G.mp3'
}
]
}
},
components:{
player
}
};
</script>
And then the component with the audio tag:
<template>
<div>
<h3>{{ name }}</h3>
<audio controls controlsList="nodownload">
<source ref="player" v-bind:src="file" type="audio/mpeg">
</audio>
</div>
</template>
<script>
export default{
name: "player",
props: {
name: {
type: String,
default: null
},
file:{
type: String,
default: null
}
}
}
</script>
Here you can see that my data is being passed successfully.
And you see that the source attribute is being loaded correctly as well.
This is what I get, and you can see the h3 loads fine, so I know the data is being passed. But the element is greyed out.
To test it out, I tried just hard coding the file path:
<source src="../assets/tracks/5G.mp3">
And that works just fine:
But I don't want to do it like that because I have about 11 tracks to do, so I would like for it to load from the data being passed so that I can reuse the component. Any ideas?
try using something like this in your Vue v-for I use this when src is not loading on the img tag.
:src="getSrc(x.src)"
methods: {
getSrc(src) {
return require("../assets/" + src);
}
}
Your watch function on file prop is never triggered because your file name is static, it never triggers a change on the watcher, before mounting the component, at least in the code you provided it is that way.
From Vue.js API vm.$watch
Watch an expression or a computed function on the Vue instance for changes. The callback gets called with the new value and the old value.
I'm attempting to create components using Vue, so that I can remove a lot of duplicated HTML in a site I'm working on.
I have a <ym-menucontent> component, which within it will eventually have several other components, conditionally rendered.
While doing this I've hit a wall and so have simplified everything to get to the root of the problem.
When rendering the ym-menucontent component the first sub-component is the only one which gets rendered and I can't work out why or how to get around it...
<template id="menucontent">
<div>
<ym-categories :menuitem="menuitem"/>
<ym-rootmaps :menuitem="menuitem"/>
<p>1: {{menuitem.rootMapsTab}}</p>
<p>2: {{menuitem.exploreTab}}</p>
</div>
</template>
<template id="rootmaps">
<div>Root Maps</div>
</template>
<template id="categories">
<div>Categories</div>
</template>
app.js
Vue.component('ym-menucontent', {
template: '#menucontent',
props: ['menuitem'],
data: function() {
return {
customMenu: window.customMenuJSON
}
}
});
Vue.component('ym-rootmaps', {
template: '#rootmaps',
props: ['menuitem'],
data: function() {
return {
customMenu: window.customMenuJSON,
rootMaps: window.rootAreas
}
}
});
Vue.component('ym-categories', {
template: '#categories',
props: ['menuitem'],
data: function() {
return {
customMenu: window.customMenuJSON,
rootMaps: window.rootAreas
}
}
});
usage...
<div
v-for="mi in customMenu.topLevelMenuItems"
:id="mi.name"
class="page-content tab swiper-slide">
<ym-menucontent :menuitem="mi"/>
</div>
Output
<div>Categories</div>
if I switch around ym-cateogries and ym-rootmaps then the output becomes...
<div>Root Maps</div>
if I remove both then I see...
<p>1: true</p>
<p>2:</p>
I'd expect to see a combination of all of them...
<div>Categories</div>
<div>Root Maps</div>
<p>1: true</p>
<p>2:</p>
This is probably because you're using self-closing components in DOM templates, which is recommended against in the style-guide ..
Unfortunately, HTML doesn’t allow custom elements to be self-closing -
only official “void” elements. That’s why the strategy is only
possible when Vue’s template compiler can reach the template before
the DOM, then serve the DOM spec-compliant HTML.
This should work for you ..
<template id="menucontent">
<div>
<ym-categories :menuitem="menuitem"></ym-categories>
<ym-rootmaps :menuitem="menuitem"></ym-rootmaps>
<p>1: {{menuitem.rootMapsTab}}</p>
<p>2: {{menuitem.exploreTab}}</p>
</div>
</template>
<div
v-for="mi in customMenu.topLevelMenuItems"
:id="mi.name"
class="page-content tab swiper-slide">
<ym-menucontent :menuitem="mi"></ym-menucontent>
</div>
I'm trying to create a component in vuepress to display an image with its caption. When I hardcode the image path, the image appears but this way I will not have a reusable component. I already try with props, but it doesn't work either.
Here is how I already tried:
<template>
<figure>
<!-- <img src="../../guides/contribute/images/typora-tela1.png" alt=""/> -->
<img :src="imagesrc" alt=""/>
<figcaption>Legenda: {{ caption }} - {{ src }}</figcaption>
</figure>
</template>
<script>
...
props: ['src'],
computed: {
imagesrc () {
return '../../guides/contribute/images/' + this.src // this.image
}
}
...
</script>
On my README.md I call the component like this: <captioned-image src="filename.png" caption="Caption Example" /> but the image doesn't appear.
How can I fix this issue? Is it possible to do this with markdown only?
In markdown (without a Vue component) you can use html,
<figure>
<img src='../../guides/contribute/images/typora-tela1.png'>
<figcaption>Caption Example</figcaption>
</figure>
To make the CaptionedImage.vue component work I think you need to put the images in the /.vuepress/public/ folder.
The difference (as I understand it) is that within the markdown the image path is handled at compile time, but for the component the image path is resolved at runtime.
Anything placed in /.vuepress/public/ is available at runtime referenced from the page root.
This works for me:
project structure
<project root folder>
docs
.vuepress
components
CaptionedImage.vue
public
images
myImage.jpg
ReadMe.md
CaptionedImage.vue
<template>
<figure>
<img :src="imagesrc" alt=""/>
<figcaption>Legenda: {{ caption }} - {{ src }}</figcaption>
</figure>
</template>
<script>
export default {
props: ['src', 'caption'],
computed: {
imagesrc () {
return './images/' + this.src
}
}
}
</script>
ReadMe.md
<CaptionedImage src="myImage.jpg" caption="Caption Example"></CaptionedImage>
Thanks to Richard. Based on what he did, I changed a little. So that
image shows in the center.
width can be changed very easily.
caption show in small size and italic font.
Cimg.vue
<template>
<figure align='center'>
<img :src="imagesrc" :width="width" alt=""/>
<figcaption><i><small>{{ caption }}</small></i></figcaption>
</figure>
</template>
<script>
export default {
props: ['src', 'caption', 'width'],
computed: {
imagesrc () {
return this.src
}
}
}
</script>
ReadMe.md
For example:
<Cimg src='/images/ml/GAN/永野芽郁.jpg' width='100%' caption="《半分、青い》 玲爱"/>
Here is the final effect.