Loading inline SVG in vuejs via prop into a component - javascript

I am trying to load an inline svg into my component.
App.vue
template
<vue-anime :url="'planetiaermma.svg'" />
anime.vue
<div>
<div v-html="this.loadSVG()"></div>
<img :src="this.svgfile()" alt />
</div>
props: ["url"],
methods: {
svgfile() {
var img = this.getImgUrl(this.url);
return img;
},
getImgUrl(pic) {
return require("../assets/" + pic);
},
loadSVG() {
var img = this.getImgUrl(this.url);
var svgraw;
this.$nextTick(() => {
fetch(img)
.then(res => res.text())
.then(svg => {
svgraw = svg;
});
});
return svgraw;
},
mounted() {
this.loadSVG();
}
}
};
firing svgfile() into a :src works
firing loadSVG() should load it inline, does not seem to however, even though the return svgraw; is in a string of html/xml.

You can simply use something like vue-inline-svg for this:
npm install vue-inline-svg
and register the component globally in your main.js file:
import Vue from 'vue'
import App from './App.vue'
import InlineSvg from 'vue-inline-svg';
Vue.component('inline-svg', InlineSvg);
new Vue({
render: h => h(App),
}).$mount('#app')
After that you can use the component anywhere inside your templates
Note: if you use vue-loader assets or vue-cli, then paths like '../assets/my.svg' will not be handled by file-loader automatically like vue-cli do for tag, so you will need to use it with require:
<template>
<div>
<inline-svg :src="require(`../assets/${url}`)" />
<!-- You also can specify some props -->
<!-- width="150" -->
<!-- height="150" -->
<!-- :fill="false" -->
</div>
</template>
and get rid of that messy code.

I'm not sure what are you getting from getImgUrl() but could you try this?
mounted() {
this.$nextTick(() => {
this.loadSVG();
});
}
You might also try this to be sure it's not from missing value:
<img v-if="svgURL" :src="svgURL" alt />
data() {
return {
svgURL: null
}
}
mounted() {
loadSVG() {
this.svgURL = 'YOUR VALUE'
}
}
Please avoid using this inside the template.

App.vue
<vue-anime :url="'planet.svg'" />
anime.vue component
<template>
<div>
<div v-html="mysvgfile"></div>
</div>
</template>
export default {
props: ["url"],
data() {
return {
mysvgfile: ""
};
},
methods: {
svgfile() {
var img = this.getImgUrl(this.url);
return img;
},
getImgUrl(pic) {
return require("../assets/" + pic);
},
loadSVG() {
var img = this.getImgUrl(this.url);
var outer = this;
this.$nextTick(() => {
fetch(img)
.then(res => res.text())
.then(svg => {
outer.mysvgfile = svg;
});
});
}
},
mounted() {
this.loadSVG();
}
};

Related

Active events in vue instance encapsulated inside shadowDOM

I'm searching to trigger event from a vue instance which is encapsulated inside a shadowDOM.
For the moment, my code create a new Vue instance inside a shadowDOM and print the template without the style (because nuxt load the style inside the base vue instance).
I can call methods of each instance
But, when i'm trying to add an event / listener on my component, it doesn't work.
DOM.vue
<template>
<section ref='shadow'></section>
</template>
<script>
import bloc from '#/components/bloc.vue'
import Vue from 'vue'
export default {
data() {
return {
vm: {},
shadowRoot: {},
isShadowReady: false
}
},
watch: {
isShadowReady: function() {
let self = this
this.vm = new Vue({
el: self.shadowRoot,
components: { bloc },
template: "<bloc #click='listenClick'/>",
methods: {
listenClick() { console.log("I clicked !") },
callChild() { console.log("I'm the child !") }
},
created() {
this.callChild()
self.callParent()
}
})
}
},
mounted() {
const shadowDOM = this.$refs.shadow.attachShadow({ mode: 'open' })
this.shadowRoot = document.createElement('main')
shadowDOM.appendChild(this.shadowRoot)
this.isShadowReady = true
},
methods: {
callParent() {
console.log("I'am the parent")
},
}
}
</script>
bloc.vue
<template>
<div>
<p v-for='word in words' style='text-align: center; background: green;'>
{{ word }}
</p>
</div>
</template>
<script>
export default {
data() {
return {
words: [1,2,3,4,5,6]
}
}
}
</script>
Any idea ?
Thank you
Hi so after i watched at your code i think it's better to use the $emit to pass event from the child to the parent and i think it's more optimized then a #click.native
template: "<bloc #someevent='listenClick'/>",
<template>
<div #click="listenClickChild">
<p v-for='(word, idx) in words' :key="idx" style='text-align: center; background: green;'>
{{ word }}
</p>
</div>
</template>
<script>
export default {
data() {
return {
words: [1,2,3,4,5,6]
}
},
methods: {
listenClickChild() {
this.$emit('someevent', 'someValue')
}
}
}
</script>
bloc #click.native='listenClick'/>

CSS of component balise doesn't load inside spefic component

I've a problem to load the css of my bloc component.
The webpage component allow to create an iframe and set some content inside easily.
It load correctly the template and script tag but not the css (it doesn't load).
Sometime it works, most of the time, it didn't.
I was thinking that it was a problem with the loading of the component but no.
If I load the component before or after the render of my "webpage" component : it don't load.
I've try with the auto import to true and after to false, but it solve nothing.
I have 2 components : webpage and bloc.
bloc.vue
<template>
<div class="bloc">
<p>Le texte</p>
</div>
</template>
<style>
.bloc {
background-color: blue;
padding: 20px;
}
</style>
webpage.vue
<script>
import Vue from "vue";
export default {
props: {
css: {
type: String,
required: false,
},
},
data() {
return {
load: false,
};
},
render(h) {
return h("iframe", {
on: { load: this.renderChildren },
});
},
beforeUpdate() {
//freezing to prevent unnessessary Reactifiation of vNodes
this.iApp.children = Object.freeze(this.$slots.default);
},
mounted() {
if (!this.load) this.renderChildren();
},
methods: {
// https://forum.vuejs.org/t/render-inside-iframe/6419/12
renderChildren() {
this.load = true;
const children = this.$slots.default;
const head = this.$el.contentDocument.head;
const body = this.$el.contentDocument.body;
let style = this.$el.contentDocument.createElement("style");
style.textContent += this.$props.css;
head.appendChild(style);
const iApp = new Vue({
name: "iApp",
// freezing to prevent unnessessary Reactifiation of vNodes
data: { children: Object.freeze(children) },
render(h) {
return h("body", this.children);
},
});
this.iApp = iApp; // cache instance for later updates
this.iApp.$mount(body); // mount into iframe
},
},
};
</script>
app.vue
<template>
<Webpage>
<component :is="name"></component>
</Webpage>
</template>
<script>
import bloc from "#/components/Bloc";
import Webpage from "#/components/Webpage";
export default {
components: {
bloc,
Webpage,
},
computed: {
name() {
return "bloc";
},
},
};
</script>
Do you have an idea where this might come from ?
The codesanbox : https://codesandbox.io/s/error-style-component-import-1t1hs?file=/pages/index.vue
Thank you.
Probably Webpage component overrides it.
Try moving your style to index and <Webpage class="bloc">

VUEJS i want import image file directly in variable

i want import image file from specific link into my vue variable can someone help me
i tried with required but it doesn't work this.imagefile = require(linkofimage)
does anyone know how to solve it
I suggest you to import first the images as constants and assign them to your vue data properties in hooks or methods:
<template>
<div>
<img :src="imageDirect" alt="alert">
<img :src="imageOnHook" alt="alert">
<img :src="imageOnMethod" alt="alert">
<img :src="imageRequire" alt="alert">
</div>
</template>
<script>
const image = require('#/assets/alert_logo_card.png')
export default {
data: () => ({
imageDirect: image,
imageOnHook: null,
imageOnMethod: null,
imageRequire: null,
}),
mounted() {
this.imageOnHook = image
this.imageRequire = require('#/assets/alert_logo_card.png')
this.assignImage()
},
methods: {
assignImage() {
this.imageOnMethod = this.imageDirect
}
}
}
</script>
I'm using the same image just for example purpose.
Something like this also will work:
methods: {
assignImage() {
this.imageOnMethod = this.imageDirect
}
}
Showing an image from the network:
<template>
<div>
<img :src="imageFromUrl" alt="alert" width="200" height="200">
</div>
</template>
<script>
export default {
data: () => ({
imageFromUrl: null
}),
mounted() {
setTimeout(() => {
this.requestImage()
}, 2000);
},
methods: {
requestImage() {
const responseFromNetwork = 'https://upload.wikimedia.org/wikipedia/commons/thumb/1/12/Google_Photos_icon_%282020%29.svg/1024px-Google_Photos_icon_%282020%29.svg.png'
this.imageFromUrl = responseFromNetwork
}
}
}
</script>

how to move items to another component by click - vue.js

I was wondering how can I move my items -> book, from one component to another. I took this books from api and I show them in the list, so I have an ID.
<template>
<v-flex v-for="(book, index) in allBooks">
<div>Title: {{ book.title }}</div>
<i #click="markAsFavorite(book)" :class="{isActive: isMark}" class="fas fa-heart"></i>
</template>
//component books
<script>
name: 'Books',
data () {
return {
allBooks: [],
isMark: false,
favouriteBooks: []
}
},
mounted() {
axios.get("https://www.googleapis.com/books/v1/volumes?q=id:" + this.id)
.then(response => {
console.log(response)
this.allBooks = response.data.items.map(item => ({...item, isMark: false}))
console.log(this.allBooks)
})
.catch(error => {
console.log(error)
})
},
methods: {
markAsFavorite(book) {
this.isMark = !this.isMark
let favouriteAllBooks = this.favouriteBooks.push(book => {
book.id = // i dont know what?
})
},
}
</script>
//component favourite
<template>
<div class=showFavouriteBook>
<p></p>
</div>
</template>
I tried to compare this marked book ID to something, and then this array with marked books show in second template favourite. But I have no idea how to do this. Maybe somebody can prompt me something?
You should use a global eventBus for that. An 'eventBus' is another instance of Vue which is used to pass data via components tied to the main application.
At the root script of your application append the following:
const eventBus = new Vue({
data: function() {
return {
some_var: null,
}
}
});
You can use Vue mixin to have your event bus accessible globally easily:
Vue.mixin({
data: function() {
return {
eventBus: eventBus,
}
}
});
Now when you want to pass data between components you can use the bus:
Component 1
// for the sake of demo I'll use mounted method, which is invoked each time component is mounted
<script>
export default {
mounted: function() {
this.eventBus.some_var = 'it works!'
}
}
</script>
Component 2
<template>
<div>
{{ eventBus.some_var }} <!-- it works -->
</div>
</template>
In addition you can use $emit and $on.
Component 1
// for the sake of demo I'll use mounted method, which is invoked each time component is mounted
<script>
export default {
mounted: function() {
// emit 'emittedVarValue' event with parameter 'it works'
this.eventBus.$emit('emittedVarValue', 'it works!')
}
}
</script>
Component 2
<template>
<div>
{{ some_var }} <!-- "it works" once eventBus receives event "emittedVarValue" -->
</div>
</template>
<script>
export default {
data: function() {
return {
some_var: null
}
},
mounted: function() {
// waiting for "emittedVarValue" event
this.eventBus.$on('emittedVarValue', (data)=>{
this.some_var = data;
})
}
}
</script>
Hope this answer helps you.

Vuejs - Child component method run before parent query done running

I'm working on a basic POC app that has a Solr search function on the front that finds products, and then links in the search results use a route to go to a Product detail page.
index.js
import Vue from 'vue'
import Router from 'vue-router'
import Search from '#/components/Search'
import Product from '#/components/Product'
Vue.use(Router)
export default new Router({
routes: [
{
path: '/search',
name: 'Search',
component: Search
},
{
path: '/product_display/:language/:entity_id',
name: 'product_display',
component: Product
},
]
})
Product.vue
<template>
<section class="hero is-warning">
<div class="hero-body">
<div class="container">
<h1 class="title">
{{ product.ss_field_content_title }}
</h1>
</div>
</div>
<section class="section">
<div class="container">
<productSlideshow :slideshowNid="product.is_product_slideshow"
:language="product.ss_language"></productSlideshow>
</div>
</section>
</section>
</template>
<script>
import axios from 'axios'
import ProductSlideshow from '#/components/ProductSlideshow'
export default {
name: 'Product',
components: {
ProductSlideshow
},
data () {
return {
product: {}
}
},
created () {
this.getProduct()
},
methods: {
getProduct: function () {
const params = new URLSearchParams()
var entityId = this.$route.params.entity_id
var language = this.$route.params.language
params.append('fq', 'bundle:product_display')
params.append('fq', 'entity_id:' + entityId)
params.append('fq', 'ss_language:' + language)
params.append('wt', 'json')
params.append('rows', 1)
axios.get('https://my.solrurl.com/solr/indexname/select', {
params: params
})
.then(response => {
this.product = response.data.response.docs[0]
})
.catch(e => {
this.errors.push(e)
})
}
},
watch: {
'$route': 'getProduct'
}
}
</script>
<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped>
</style>
Getting to this page works fine. The problem is getting the necessary params to the child ProductSlideshow component.
<template>
<h1>{{ slideshow.label }}</h1>
</template>
<script>
import axios from 'axios'
export default {
name: 'ProductSlideshow',
props: {
slideshowNid: {
type: Number,
required: true
},
language: {
type: String,
required: true
}
},
data () {
return {
slideshow: {},
slides: {}
}
},
created () {
this.getSlideshow()
},
methods: {
getSlideshow: function () {
var language = this.language
// Get slideshow record from Solr.
const params = new URLSearchParams()
params.append('fq', 'bundle:slideshow')
params.append('fq', 'entity_id:' + this.slideshowNid)
params.append('fq', 'ss_language:' + language)
params.append('fq', 'entity_type:node')
params.append('wt', 'json')
params.append('rows', 1)
axios.get('https://my.solrurl.com/solr/indexname/select', {
params: params
})
.then(response => {
this.slideshow = response.data.response.docs[0]
this.slides = this.slideshow.sm_field_slideshow_home
})
}
},
watch: {
'$route': 'getSlideshow'
}
}
</script>
<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped>
</style>
What I'm seeing is no data returned from the Solr query for the slideshow record, and when inspecting the Solr query, it was because language was undefined in the query, so nothing could be returned. By using various debugger breakpoints, I figured out that what's happening is that getSlideshow() is being called before the results are returned from getProduct() in the Product component. I tried removing the created() method in the ProductDisplay component, but that didn't make a difference.
What do I need to change so that my getSlideshow() method in the ProductSlideshow component doesn't get called until the query from the Product component has been completed?
Your issue connected with the fact, that Vue fires created hooks in sync way, so all children templates are created and mounted before the response from API comes.
The solution is to use Conditional Rendering
In your Product.vue component create some Boolean value like isProductFetched and render the child only when this value will be true.
<template>
<section class="hero is-warning">
<div class="hero-body">
<div class="container">
<h1 class="title">
{{ product.ss_field_content_title }}
</h1>
</div>
</div>
<section class="section">
<div class="container">
<productSlideshow
v-if="isProductFetched"
:slideshowNid="product.is_product_slideshow"
:language="product.ss_language"></productSlideshow>
</div>
</section>
</section>
</template>
<script>
import axios from 'axios'
import ProductSlideshow from '#/components/ProductSlideshow'
export default {
name: 'Product',
components: {
ProductSlideshow
},
data () {
return {
isProductFetched: false,
product: {}
}
},
created () {
this.getProduct()
},
methods: {
getProduct: function () {
const params = new URLSearchParams()
var entityId = this.$route.params.entity_id
var language = this.$route.params.language
params.append('fq', 'bundle:product_display')
params.append('fq', 'entity_id:' + entityId)
params.append('fq', 'ss_language:' + language)
params.append('wt', 'json')
params.append('rows', 1)
axios.get('https://my.solrurl.com/solr/indexname/select', {
params: params
})
.then(response => {
this.product = response.data.response.docs[0]
// At this point, the product is fetched
this.isProductFetched = true
})
.catch(e => {
this.errors.push(e)
})
}
},
watch: {
'$route': 'getProduct'
}
}
</script>
<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped>
</style>
As a result, the child component won't be rendered until this value becomes true. Also, do not forget to handle the error case.

Categories

Resources