How to handle infinite GET Image requests when dealing with broken image url in Vue 2? - vue.js

I'm developing a new version of my website in Vue 2 and I have created a component to handle image broken links, swapping the url domain, in order to display images stored in my local host and also those stored in the production server. The problem is that sometimes the image filename in the server is different than what is set on my local host database (because the user changed the image on the production live version), and then I get infinite requests looping in the console since neither image url are valid.
I'm also using Bootstrap-Vue to lazy-load the images.
This is my code:
<template>
<b-img-lazy :src="'https://example.com/uploads/' + photo" #error.native="replaceSrc" :alt="titulo" :title="title" class="thumb"></b-img-lazy>
</template>
<script>
export default {
name: "ImgCarousel",
props: {
photo: {
type: String
},
title: {
type: String
}
},
data: function () {
return {
index: null
}
},
methods: {
replaceSrc(e) {
e.target.src = 'http://localhost:8888/uploads/' + this.photo;
},
}
}
</script>
How do I stop the GET requests from entering a infinite loop?

I also reached the solution below. Probably not elegant as Nikola's answer, although it needs less coding. Basically it adds a counter on each call for "replaceSrc" to prevent looping on the broken links:
<template>
<b-img-lazy :src="'https://example.com/uploads/' + photo" #error.native="replaceSrc" :alt="title" :title="title" class="thumb"></b-img-lazy>
</template>
<script>
export default {
name: "ImgCarousel",
props: {
photo: {
type: String
},
title: {
type: String
}
},
data: function () {
return {
counter: 0
}
},
methods: {
replaceSrc(e) {
if (this.counter == 0) {
e.target.src = 'http://localhost:8888/uploads/' + this.photo;
this.counter ++;
} else if (this.counter == 1) {
e.target.src = 'https://via.placeholder.com/300x190';
this.counter ++;
}
},
}
}
</script>
BTW the "index: null" data on my question was a wrong code I forgot to remove.

Try to check if image exists:
new Vue({
el: '#demo',
data: function () {
return {
startImg: 'https://picsum.photos/150',
index: null,
photo: '100',
title: '',
titulo: '',
url: 'https://picsum.photos/'
}
},
methods: {
replaceSrc(e) {
this.checkIfImageExists(this.url + this.photo, (exists) => {
exists ?
e.target.src = this.url + this.photo :
this.titulo = 'no image'
})
},
checkIfImageExists(url, callback) {
const img = new Image();
img.src = url;
if (img.complete) {
callback(true);
} else {
img.onload = () => callback(true)
img.onerror = () => callback(false)
}
},
change() {
this.startImg = this.startImg ? '' : 'https://picsum.photos/150'
},
breakSource() {
this.startImg = "wrong url"
this.photo = "wrong url"
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<link type="text/css" rel="stylesheet" href="https://unpkg.com/bootstrap/dist/css/bootstrap.min.css" />
<link type="text/css" rel="stylesheet" href="https://unpkg.com/bootstrap-vue#latest/dist/bootstrap-vue.min.css" />
<script src="https://unpkg.com/bootstrap-vue#latest/dist/bootstrap-vue.min.js"></script>
<script src="https://unpkg.com/bootstrap-vue#latest/dist/bootstrap-vue-icons.min.js"></script>
<div id="demo">
<div>
<b-img-lazy :src="startImg" #error.native="replaceSrc" :title="title" :alt="titulo" class="thumb"></b-img-lazy>
</div>
<button #click="change">Change source</button>
<button #click="breakSource">Break source</button>
</div>

Related

Sending and initializing data to another component with vue 3

what I want to do is to send a value to the file "Notification.vue" when the button is clicked in the hello word file and I want to show Notification
toastify
helloword.vue
<template>
<button #click="toast">Toast it!</button>
</template>
<script>
export default {
name: "helloword",
methods: {
toast() {
}
}
}
</script>
Notification.vue
<script setup>
import {createToast} from 'mosha-vue-toastify';
import 'mosha-vue-toastify/dist/style.css'
const props = defineProps({title: String})
const toast = () => {
createToast(props.title)
}
</script>
I just created a demo with Vue 2 for your understanding how components will communicate. You can migrate this in Vue 3.
Demo :
Vue.use(VueToast, {
// global options
});
Vue.component('notification', {
props: ['title'],
mounted() {
if (this.title) {
this.$toast.open({
message: this.title,
type: "success",
duration: 5000,
dismissible: true
})
}
}
});
var app = new Vue({
el: '#app',
data: {
title: null,
},
methods: {
toast: function() {
this.title = 'Welcome!'
}
}
});
<script src="https://cdn.jsdelivr.net/npm/vue#2.6"></script>
<script src="https://cdn.jsdelivr.net/npm/vue-toast-notification#0.6/dist/index.min.js"></script>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/vue-toast-notification#0.6/dist/theme-sugar.css"/>
<div id="app">
<button #click="toast">Toast it!</button>
<notification v-if="title" :title="title"></notification>
</div>

Vue js: set notification count to zero

in my Vue Js code below, i created count for notification socket, so whenever a new notification received it count+ and appears beside the icon ... now i wanted to set back the count to zero when a user click on notification icon but i couldn't figure out how to do it.
below in app.vue i set a prop called number and i pass it to the sidebar to appear on all project pages , this number is the count of the notifications .. is there a way to set it back to zero after user click on the icon in the side bar?
<template>
<router-view :number="count" #send="getNewCount">
<sidebar :number="count" />
</router-view>
</template>
<script>
import sidebar from "#/views/Layout/DashboardLayout.vue";
import axios from "axios";
import {
io
} from "socket.io-client";
let socket = io("h***********/");
export default {
components: {
sidebar,
},
data() {
return {
user2: JSON.parse(sessionStorage.getItem("user")),
count: 0,
today: null,
};
},
props: {
number: {
type: Number,
default: 0,
},},
async created() {
console.log(this.count);
const response = await axios.get(
`https://l*********rs/api/${this.user2._id}/`, {
headers: {
Authorization: "Bearer " + sessionStorage.getItem("user"),
},
}
);
// await this.$store.dispatch("user", response.data.response);
socket.emit("user_connected", this.user2.username);
// console.log(this.user2,"userr")
socket.on("verify_connection", (data) => {
this.verify_connection = data;
console.log(data, "s")
});
socket.emit("user_connected", this.user2.username);
socket.on("verify_connection", (data) => {
console.log("heyy", data);
this.verify_connection = data;
});
socket.on("updated_flat", (data) => {
console.log("heyy", data);
this.makeToast(" success ", "'b-toaster-top-center");
});
socket.on("test", (data) => {
console.log("heyy", data);
// this.makeToast("success", "b-toaster-top-right");
});
;
},
methods: {
// playSound() {
// var audio = document.getElementById("audio");
// audio.play();
// },
getNewCount(data) {
this.count = data;
},
makeToast(variant = null, toaster, append = false) {
this.$bvToast.toast(" edited ", {
title: "BootstrapVue Toast",
variant: variant,
autoHideDelay: 10000,
toaster: toaster,
position: "top-right",
appendToast: append,
});
// this.playSound();
this.count = this.count + 1;
console.log(this.count,"count");
},
}
}
</script>
sidebar:
<template>
<div class="wrapper">
<side-bar >
<template slot="links">
<sidebar-item v-if="roles ==='Admin'"
:link="{
name: ' notifications',
path: '/notifications',
icon: 'ni ni-bell-55 text-green'
}">
</sidebar-item>
<p class="notifCount" v-if="roles ==='Admin'"> {{ number }} </p>
</template>
</side-bar>
<div class="main-content">
<dashboard-navbar :type="$route.meta.navbarType"></dashboard-navbar>
<div #click="$sidebar.displaySidebar(false)">
<fade-transition :duration="200" origin="center top" mode="out-in">
<!-- your content here -->
<router-view></router-view>
</fade-transition>
</div>
<content-footer v-if="!$route.meta.hideFooter"></content-footer>
</div>
</div>
</template>
<script>
/* eslint-disable no-new */
import PerfectScrollbar from 'perfect-scrollbar';
import 'perfect-scrollbar/css/perfect-scrollbar.css';
function hasElement(className) {
return document.getElementsByClassName(className).length > 0;
}
function initScrollbar(className) {
if (hasElement(className)) {
new PerfectScrollbar(`.${className}`);
} else {
// try to init it later in case this component is loaded async
setTimeout(() => {
initScrollbar(className);
}, 100);
}
}
import DashboardNavbar from './DashboardNavbar.vue';
import ContentFooter from './ContentFooter.vue';
import DashboardContent from './Content.vue';
import { FadeTransition } from 'vue2-transitions';
export default {
components: {
DashboardNavbar,
ContentFooter,
DashboardContent,
FadeTransition
},
props: {
number: {
type: Number,
default: 0,
},
},
methods: {
initScrollbar() {
let isWindows = navigator.platform.startsWith('Win');
if (isWindows) {
initScrollbar('sidenav');
}
},
},
computed: {
roles() {
let roles = JSON.parse(sessionStorage.getItem('user')).role;
return roles;
},},
mounted() {
this.initScrollbar()
}
};
</script>
Emit an event from sidebar and handle the event in the main app.
main app:
<template>
<!-- ... -->
<sidebar :number="count" #reset="onReset" />
<!-- ... -->
</template>
<script>
export default {
// ...
methods: {
onReset() {
// api calls, etc.
this.count = 0;
}
}
}
</script>
sidebar:
<template>
<!-- ... -->
<button #click="$emit('reset')">Reset notification count</button>
<!-- ... -->
</template>

How to pass dynamic props to vue's render function?

I try to render components dynamically based on descriptions.
From
{component: 'customComponent', props: {value: "val1"}, ...}
I'd render
<custom-component :value="val1" #input="v=>val1=v" />`
I aim to do this for arbitrary events and dynamic props.
I have no idea though how to pass dynamic props to render.
Partial solution:
A solution that works but re-renders everytime val1 changes, based on (https://symfonycasts.com/screencast/vue/vue-instance) is
render: function(h){
const template = "...building up the html here..."
return Vue.compile(template).render.call(this, h);
}
My attempt using the VueJS docs
I could not find in the docs on render how I could pass dynamic variables.
In the minimal implementation you can see how far I've got, if you can help me finish it, it would be awesome!
Minimal implementation so far
I expect to see 'hello' instead of 'values.value1' and values.value1 should update once I change the text in the text box.
demo.html:
<!DOCTYPE html>
<html>
<body>
<div id="q-app">
The text input should say 'hello' instead of 'values.value1'
<custom-component :descriptor="mainComponent"></custom-component>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue#^2.0.0/dist/vue.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/quasar#1.15.15/dist/quasar.umd.min.js"></script>
<script>
Vue.component('custom-component', {
props: ['descriptor'],
render: function (createElement) {
const attributes = {
on: this.descriptor.events,
props: this.descriptor.props
}
return createElement(
this.descriptor.component,
attributes)
}
})
const app = new Vue({
el: '#q-app',
data: function(){
return {
mainComponent: {
component: 'q-input',
props: {
value: 'values.value1'
},
events: {
input: value => this.values.value1 = value
}
},
values: {
value1: 'hello'
}
}
}
})
</script>
</body>
I guess, I have fixed your example.
Of course, you can watch the value in the main app. But it is better to sent an input event with the value.
Added the reference to the component
ref="mc"
and the input event binding
#input="logEventValue"
Vue.component('custom-component', {
props: ['descriptor'],
render: function (createElement) {
const attributes = {
on: this.descriptor.events,
props: this.descriptor.props
}
return createElement(
this.descriptor.component,
attributes)
}
})
const app = new Vue({
el: '#q-app',
data: function(){
return {
mainComponent: {
component: 'q-input',
props: {
value: 'hello'
},
events: {
input: value => {
this.values.value1 = value;
// with ref="mc"
this.$refs.mc.$emit('input', value);
// or over the list of children
this.$children[0].$emit('input', value);
}
}
},
values: {
value1: 'hello'
}
}
},
watch: {
'values.value1': (newVal) => console.log(`1. watcher: ${newVal}`),
// or deeply
values: {
handler(newVal) {
console.log(`2. watcher: ${newVal.value1}`)
},
deep: true,
}
},
methods: {
logEventValue(value) {
console.log(`logEventValue: ${value}`);
}
}
})
<div id="q-app">
<custom-component ref="mc" #input="logEventValue" :descriptor="mainComponent"></custom-component>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue#^2.0.0/dist/vue.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/quasar#1.15.15/dist/quasar.umd.min.js"></script>

Improving performance of Vue.js application

In our application, there is large set of data (json ~ 630KB) getting populated which consumes around 219.28 MB of JavaScript memory displayed in chrome task manager.
It loads the data from the api and uses vuex to store the data and generates the menu recursively.
Since it is a big application, I mimicked the same behaviour with a sample vue.js application by loading 100000 records to understand how the memory utilization works.
Each time "Run test" is clicked, 200 MB memory is consumed in the task manager and is not released until so much time even if the page is refreshed.
I tried to use "beforeDestroy" hook to clear the object from vuex but it is not working. I would like to know if the data itself is causing the slow performance or any other improvements can be done.
<html>
<head>
</head>
<body>
<div id="myApp">
<button #click="setCounterModule()">Counter</button>
<button #click="setCustomersModule">Customers</button>
<br />
<br />
<div>
<button-counter v-if="currentModule=='counter'"></button-counter>
<customers v-else></customers>
</div>
</div>
<script src="vue.js"></script>
<script src="vuex.js"></script>
<script>
Vue.component('button-counter', {
data: function () {
return {
count: 0
}
},
template: '<button v-on:click="count++">You clicked me {{ count }} times.</button>'
});
Vue.component('customers', {
computed: {
customers() {
return store.state.customers
}
},
template: '<div><button #click="testMe" :disabled="loading">Run Test</button><div v-for="c in customers">{{ c.name }}</div></div>',
data: function() {
return {
loading: false
}
},
methods: {
testMe: function () {
this.loading = true;
let customers = [];
for (var i=0; i < 100000; i++) {
let customer = {'name': 'John' +i, 'address': 'Addr' + i};
customers.push(customer);
}
store.commit('assignCustomers', customers);
this.loading = false;
}
},
beforeDestroy() {
// console.log('vuex destroyed');
// store.commit('assignCustomers', null);
}
});
const store = new Vuex.Store({
state: {
customers: null
},
mutations: {
assignCustomers (state, customers) {
state.customers = customers;
}
}
});
var app = new Vue( {
el: "#myApp",
data: function() {
return {
currentModule: "counter"
}
},
methods: {
setCounterModule: function() {
this.currentModule = "counter";
},
setCustomersModule: function() {
this.currentModule = "customers";
}
}
});
</script>
</body>
</html>

Vue computed property not recomputing as I would have expected

I'm trying to create a generic search component with an event that gets emitted with the search string back to the parent so the parent can actually filter the results.
With the code below why does computed.filteredDocuments not recompute when the value of this.searchCriteria changes and how can I tweak my code so that it does recompute when updatedSearchString is called?
Parent component
<template>
<search :searchCriteria="searchCriteria" #searchString="updatedSearchString" />
<div v-for="(doc, index) in filteredDocuments" v-bind:key="index">
<div>{{doc.filename}}</div>
</div>
</template>
<script>
import store from '../store/index'
import { mapState } from 'vuex'
// import _ from 'lodash'
import Search from '../components/search'
export default {
name: 'Parent',
components: {
Search: Search
},
data () {
return {
searchCriteria: ''
}
},
computed: {
...mapState({
documents: state => state.documents.items
}),
filteredDocuments () {
console.log('in computed')
return _(this.documents)
.filter(this.applySearchFilter)
.value()
}
},
methods: {
updatedSearchString (searchString) {
this.searchCriteria = searchString <-- I WOULD HAVE EXPECTED BY UPDATING THIS IT WOULD TRIGGER COMPUTED.FILTEREDDOCUMENTS TO RECOMPUTE
}
},
applySearchFilter (doc) {
console.log('in applySearchFilter')
// If no search criteria return everything
if (this.searchCriteria === null) {
return true
}
if (doc.filename.toLowerCase().includes(this.searchCriteria.toLowerCase())) {
return true
}
return false
}
}
</script>
Child component
<template>
<div>
<q-search v-model="search" placeholder="Search" />
</div>
</template>
<script>
export default {
name: 'Search',
props: {
searchCriteria: { type: String, required: true }
},
data () {
return {
search: null
}
},
mounted () {
this.search = this.searchCriteria // Clone
},
watch: {
search: function (newVal, oldVal) {
// If no search criteria return everything
if (!newVal) {
this.clearSearch()
}
this.$emit('searchString', newVal) <-- THIS EMITS THE SEARCH VALUE TO THE PARENT
}
}
}
</script>
What I ran into turning your example code into a snippet was some of the camel case/kebab case issue: HTML is case-insensitive, so the event should be kebab-case, not camelCase. This example filters as expected.
new Vue({
el: '#app',
data: {
searchCriteria: '',
documents: [{
filename: 'FirstFile'
},
{
filename: 'SecondFile'
},
{
filename: 'LastOne'
}
]
},
methods: {
updatedSearchString(searchString) {
this.searchCriteria = searchString
},
applySearchFilter(doc) {
console.log('in applySearchFilter')
// If no search criteria return everything
if (this.searchCriteria === null) {
return true
}
if (doc.filename.toLowerCase().includes(this.searchCriteria.toLowerCase())) {
return true
}
return false
}
},
computed: {
filteredDocuments() {
console.log('in computed')
return this.documents
.filter(this.applySearchFilter)
}
},
components: {
search: {
template: '#child-template',
props: {
searchCriteria: {
type: String,
required: true
}
},
computed: {
search: {
get() {
return this.searchCriteria;
},
set(value) {
this.$emit('search-string', value);
}
}
}
}
}
});
<link href="https://unpkg.com/quasar-extras#latest/material-icons/material-icons.css" rel="stylesheet"/>
<script src="https://unpkg.com/vue#latest/dist/vue.js"></script>
<link rel="stylesheet" type="text/css" href="https://unpkg.com/quasar-framework#latest/dist/umd/quasar.mat.min.css">
<script src="https://unpkg.com/quasar-framework#latest/dist/umd/quasar.mat.umd.min.js"></script>
<div id="app">
<search :search-criteria="searchCriteria" #search-string="updatedSearchString"></search>
<div v-for="(doc, index) in filteredDocuments" v-bind:key="index">
<div>{{doc.filename}}</div>
</div>
</div>
<template id="child-template">
<div>
<q-search v-model="search" placeholder="Search" />
</div>
</template>