Reputation: 65
I have a simple component for operations with goods. In this component, I can create goods and change their images. When I change the image of a certain good, the backend application performs replacing an existing image with the same name, so the image URL is not changed. As a result, images on a page do not reload until I refresh the page.
How I can make force images reloading after getting a successful server response?
That's Good component sources:
<template>
<div class="container">
<h3 class="mt-5">Goods</h3>
<div class="row mt-5">
<div class="col-md-12">
<div class="card">
<div class="card-header align-middle">
<div class="card-tools">
<button class="btn btn-success btn-sm" v-b-modal.create-good-modal>Add</button>
</div>
</div>
<div class="card-body table-responsive p-0">
<table class="table table-hover text-center">
<thead>
<tr>
<th>ID</th>
<th>Image</th>
<th>Name</th>
</tr>
</thead>
<tbody>
<tr v-for="good in goods" :key="good.id">
<td class="align-middle">{{ good.id }}</td>
<td class="align-middle">
<img class="img-thumbnail" width="60" :src="goodImageUrl(good.image)"
v-b-modal.change-good-image-modal @click="changeGoodImage(good.id)" />
</td>
<td class="align-middle">{{ good.name }}</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</div>
<b-modal ref="createGoodModal" id="create-good-modal" title="New good" hide-footer @show="resetCreateGoodModal"
@hide="resetCreateGoodModal">
<b-form @submit="createGood" class="w-100">
<b-form-group id="form-create-good-name-group" label="Name" label-for="form-create-good-name-input">
<b-form-input id="form-create-good-name-input" type="text" v-model="createGoodForm.name" required
placeholder="Good name">
</b-form-input>
</b-form-group>
<b-form-group id="form-create-good-image-group">
<b-form-file v-model="createGoodForm.image" required :state="Boolean(createGoodForm.image)"
placeholder="Choose good image">
</b-form-file>
<div class="mt-3">Selected image: {{ createGoodForm.image ? createGoodForm.image.name : '' }}</div>
</b-form-group>
<b-button type="submit" variant="primary">Create</b-button>
</b-form>
</b-modal>
<b-modal ref="changeGoodImageModal" id="change-good-image-modal" title="Change good image"
@hide="resetChangeGoodImageModal" hide-footer>
<b-form @submit="updateGoodImage" class="w-100">
<b-form-group id="form-change-good-image-group">
<b-form-file v-model="changeGoodImageForm.image" required :state="Boolean(changeGoodImageForm.image)"
placeholder="Choose good image">
</b-form-file>
<div class="mt-3">Selected file: {{ changeGoodImageForm.image ? changeGoodImageForm.image.name : ''
}}
</div>
</b-form-group>
<b-button type="submit" variant="primary">Save</b-button>
</b-form>
</b-modal>
</div>
</template>
<script>
import {objectToFormData} from "object-to-formdata";
export default {
data() {
return {
goods: [],
createGoodForm: new Form({
name: "",
image: null
}),
changeGoodImageForm: new Form({
id: {},
image: null
})
};
},
methods: {
getGoods() {
axios
.get("goods/list")
.then(response => {
this.goods = response.data;
})
.catch(error => {
console.error(error);
});
},
createGood() {
this.createGoodForm
.post("goods", {
transformRequest: [
function (data, headers) {
return objectToFormData(data);
}
]
})
.then(() => {
this.$refs.createGoodModal.hide();
this.getGoods();
})
.catch(error => {
console.log(error);
});
},
changeGoodImage(id) {
this.changeGoodImageForm.id = id;
},
updateGoodImage() {
this.changeGoodImageForm
.post("goods/" + this.changeGoodImageForm.id + "/image", {
transformRequest: [
function (data, headers) {
return objectToFormData(data);
}
]
})
.then(() => {
this.$refs.changeGoodImageModal.hide();
this.getGoods();
})
.catch(error => {
console.log(error);
});
},
goodImageUrl(filename) {
return "http://127.0.0.1:8098/api/img/goods/" + filename;
},
resetCreateGoodModal() {
this.createGoodForm.reset();
},
resetChangeGoodImageModal() {
this.changeGoodImageForm.reset();
}
},
created() {
this.getGoods();
}
};
</script>
Upvotes: 1
Views: 1322
Reputation: 65
I added next method:
refreshImageCache(filename) {
if (filename.includes('?')) {
filename = filename.split('?')[0];
}
return filename += '?ts=' + new Date().getTime();
}
and use it for update data as follows:
updateGoodImage() {
this
.changeGoodImageForm
.post("goods/" + this.changeGoodImageForm.id + "/image", {
transformRequest: [
function (data, headers) {
return objectToFormData(data);
}
]
})
.then(() => {
let good = this.tiles.find(g => g.id === this.editGoodImageForm.id);
good.image = this.refreshImageCache(tile.image);
this.$refs.editGoodImageModal.hide();
})
.catch(error => {
console.log(error);
});
}
Upvotes: 1
Reputation: 10192
My suggestion is simply to append a random cache buster to the image url - a query param the server will ignore that will force the browser to fetch a new image. This assumes your back-end will not reject the request, but most of the time this approach works. Something like /myimage.png?cacheBuster=<somethingRandom>
.
Please note that in the snippet below, a new cat image will not actually be loaded because that server doesn't have a new image each time. This is just a demonstration.
To reduce tech debt and aid in logging/debugging, I would suggest doing a little extra work and have your api return a new image version that you can append instead of just a crude cache bust. But that's a bigger project. Example /myimage.png?version=2
const originalSource = 'https://upload.wikimedia.org/wikipedia/commons/thumb/6/69/June_odd-eyed-cat_cropped.jpg/120px-June_odd-eyed-cat_cropped.jpg';
var app = new Vue({
el: '#app',
data() {
return {
imageSource: originalSource
}
},
methods: {
bustThatCache() {
this.imageSource = `${originalSource}?cacheBust=${Math.random()}`
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<p>Image Src:{{imageSource}}</p>
<img :src="imageSource"></img>
<button @click="bustThatCache">Load new image</button>
</div>
Upvotes: 1