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

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.

Related

node webkit: open thousands of urls in browser

I am using following snippet to open a link in default browser.
<template>
<div>
<a #click.prevent="fireUpLink">External Link</a>
</div>
</template>
.
<script>
/* global nw */
export default {
methods: {
fireUpLink: function() {
nw.Shell.openExternal("http://example.com/");
}
}
};
</script>
But lets say if I have thousands of links, this solution is not scalable. Is there any better way?
In a Vue SFC, it expects a referenced variable to be defined or imported in the component, or be global. If you reference it from the global window object, it should work.
window.nw.Shell.openExternal('http://example.com');
For Vue, as shown by Max, <a #click.prevent="window.nw.Shell.openExternal('http://example.com')">Link</a> works.
You could also just create a component:
<template>
<a
:href="url"
class="link"
#click.prevent="openExternal"
><slot></slot></a>
</template>
<script>
export default {
name: 'ExternalLink',
props: {
url: {
type: String,
required: true
}
},
methods: {
openExternal: function () {
window.nw.Shell.openExternal(this.url);
}
}
};
</script>
Then just reference it like this:
<external-link url="http://example.com">Link</external-link>
Alternatively you could create a mixin that has the openExternal method in it, and globally install it across all components, so you can just do <a #click.prevent="openExternal('http://example.com')>
If you are using something other than Vue, which does not use a Virtual DOM, then you could just add a class="external-link" then target all elements on the page with that class and handle them.
$('.external-link').click(function (evt) {
// Prevent the link from loading in NW.js
evt.preventDefault();
// Get the `href` URL for the current link
let url = $(this).attr('href');
// Launch the user's default browser and load the URL for the link they clicked
window.nw.Shell.openExternal(url);
});

<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>

Bootstrap image asset not processed for props style attribute on Vue Component (Nuxt.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"/>

Work process for data passing in VueJS 2 application

I am pretty new to VueJS 2, so wanted to see if I am working in the correct way. I have a system where someone uploads a file that contains data, which will then be used to create charts. So I display the uploaded files to them
<tr v-for="file in files.data" :key="file.id">
//file information
<td>
<router-link :to="{ name: file.chart, params: { fileName: file.name }}"
tag="a" exact> View Results
</router-link>
</td>
</tr>
So you can see I have a link in the table, that directs them to the chart page for the file they uploaded. It includes the params for the file name to be loaded.
On the chart page, I get the params within the created method. I then pass these to the component for the chart to be displayed
<template>
<div>
//some information
<div class="row">
<div class="col-12" id="parentDiv">
<barchart :file-name = this.fileName></barchart>
</div>
</div>
</div>
</template>
<script>
import Barchart from '../charts/Barchart';
export default {
components: {
'barchart': Barchart
},
data() {
return {
fileName: ''
}
},
created() {
this.fileName = this.$route.params.fileName;
}
}
</script>
Finally, I have the Barchart component. This is what creates the chart based on the file uploaded data.
<script>
import * as d3 from 'd3';
export default {
props: {
fileName: {
type: String,
required: true
}
},
methods: {
createBarChart() {
//d3 to create chart using the file that was uploaded
}
},
created() {
let vm = this;
d3.json('storage/' + this.fileName)
.then(function (data) {
vm.createBarChart(data);
}).catch(function (error) {
// handle error
});
}
};
</script>
To me, there seems to be a lot of passing of data from one component to the next. I pass it from the files display component (which displays all uploaded files), then to the page for the chart, which then passes it to the chart component.
The other issue is, if I am on the charts page, and I refresh the page, then the chart no longer has the filename prop and therefore the chart does not render. How would I handle this
situation?
Any advice appreciated
The reason that you are losing the chart on refresh is due to the use of the created method.
In your chart component remove the entire created method and reference the route param directly in your barchart reference, like so:
<template>
<div>
//some information
<div class="row">
<div class="col-12" id="parentDiv">
<barchart :file-name="$route.params.fileName"></barchart>
</div>
</div>
</div>
</template>
<script>
import Barchart from '../charts/Barchart';
export default {
components: {
'barchart': Barchart
},
data() {
return {
}
}
}
</script>
You may want to look into vuex to manage the data passing from parent to some deeply nested child.
Before you decide you want to persist the file in the nested component, you may want to consider if this is good UX (does it make sense that when the user refreshes the page, the old file they had uploaded is still cached?) You can look into using localStorage to store things locally so that upon refresh, the data is still there without needing the user to re-enter it.

Nuxt.js global events emitted from page inside iframe are not available to parent page

I'm trying to create a pattern library app that displays components inside iframe elements, alongside their HTML. Whenever the contents of an iframe changes, I want the page containing the iframe to respond by re-fetching the iframe's HTML and printing it to the page. Unfortunately, the page has no way of knowing when components inside its iframe change. Here's a simplified example of how things are setup:
I have an "accordion" component that emits a global event on update:
components/Accordion.vue
<template>
<div class="accordion"></div>
</template>
<script>
export default {
updated() {
console.log("accordion-updated event emitted");
this.$root.$emit("accordion-updated");
}
}
</script>
I then pull that component into a page:
pages/components/accordion.vue
<template>
<accordion/>
</template>
<script>
import Accordion from "~/components/Accordion.vue";
export default {
components: { Accordion }
}
</script>
I then display that page inside an iframe on another page:
pages/documentation/accordion.vue
<template>
<div>
<p>Here's a live demo of the Accordion component:</p>
<iframe src="/components/accordion"></iframe>
</div>
</template>
<script>
export default {
created() {
this.$root.$on("accordion-updated", () => {
console.log("accordion-updated callback executed");
});
},
beforeDestroy() {
this.$root.$off("accordion-updated");
}
}
</script>
When I edit the "accordion" component, the "event emitted" log appears in my browser's console, so it seems like the accordion-updated event is being emitted. Unfortunately, I never see the "callback executed" console log from the event handler in the documentation/accordion page. I've tried using both this.$root.$emit/this.$root.$on and this.$nuxt.$emit/this.$nuxt.$on and neither seem to be working.
Is it possible that each page contains a separate Vue instance, so the iframe page's this.$root object is not the same as the documentation/accordion page's this.$root object? If so, then how can I solve this problem?
It sounds like I was correct and there are indeed two separate Vue instances in my iframe page and its parent page: https://forum.vuejs.org/t/eventbus-from-iframe-to-parent/31299
So I ended up attaching a MutationObserver to the iframe, like this:
<template>
<iframe ref="iframe" :src="src" #load="onIframeLoaded"></iframe>
</template>
<script>
export default {
data() {
return { iframeObserver: null }
},
props: {
src: { type: String, required: true }
},
methods: {
onIframeLoaded() {
this.getIframeContent();
this.iframeObserver = new MutationObserver(() => {
window.setTimeout(() => {
this.getIframeContent();
}, 100);
});
this.iframeObserver.observe(this.$refs.iframe.contentDocument, {
attributes: true, childList: true, subtree: true
});
},
getIframeContent() {
const iframe = this.$refs.iframe;
const html = iframe.contentDocument.querySelector("#__layout").innerHTML;
// Print HTML to page
}
},
beforeDestroy() {
if (this.iframeObserver) {
this.iframeObserver.disconnect();
}
}
}
</script>
Attaching the observer directly to the contentDocument means that my event handler will fire when elements in the document's <head> change, in addition to the <body>. This allows me to react when Vue injects new CSS or JavaScript blocks into the <head> (via hot module replacement).