7

I have following problem, which I don't know how to properly tackle.

I have "list" of all purchased images on my view. I display them with v-for loop. Each image also has progress-bar element, so when user clicks on download button, downloadContent function gets executed and progress bar should be displayed.

So my html looks like this.

<section class="stripe">
    <div class="stripe__item card" v-for="(i, index) in purchasedImages">
        <progress-bar :val="i.download_progress"
                      v-if="i.download_progress > 0 && i.download_progress < 100"></progress-bar>
        <div class="card__wrapper">
            <img :src="'/'+i.thumb_path" class="card__img">
        </div>
        <div class="btn-img card__btn card__btn--left" @click="downloadContent(i.id_thumb, 'IMAGE', index)">
        </div>
    </div>
</section>

And this is my JS Code

import Vue from 'vue'
import orderService from '../api-services/order.service';
import downloadJs from 'downloadjs';
import ProgressBar from 'vue-simple-progress';

export default {
    name: "MyLocations",
    components: {
        ProgressBar: ProgressBar
    },
    data() {
        return {
            purchasedImages: {},
            purchasedImagesVisible: false,
        }
    },
    methods: {
        getUserPurchasedContent() {
            orderService.getPurchasedContent()
                .then((response) => {
                    if (response.status === 200) {

                        let data = response.data;
                        this.purchasedImages = data.images;

                        if (this.purchasedImages.length > 0) {
                            this.purchasedImagesVisible = true;
                            // Set download progress property
                            let self = this;
                            this.purchasedImages.forEach(function (value, key) {
                                self.purchasedImages[key].download_progress = 0;
                            });
                        }
                    }
                })
        },
        downloadContent(id, type, index) {
            let self = this;
            orderService.downloadContent(id, type)
                .then((response) => {
                    let download = downloadJs(response.data.link);
                    download.onprogress = function (e) {
                        if (e.lengthComputable) {
                            let percent =  e.loaded / e.total * 100;
                            let percentage = Math.round(percent);
                            if (type === 'IMAGE') {
                            // Is this proper way to set one field reactive?
                         self.purchasedImages[index].download_progress = percentage;
                                if (percentage === 100) {
                                    self.purchasedImages[index].download_progress = 0;
                                }
                            }
                        }
                    }
                })
        },
    },
    mounted: function () {
        this.getUserPurchasedContent();
    }
};

So the problem is. When user clicks on download button, download starts to execute and I get downloaded content, but I don't see progress bar. So I wonder, is this a proper way to set element reactive? How should my implementation look like? How to properly set self.purchasedImages[index].download_progress object key value, so progress bar will be visible?

If you need any additional informations, please let me know and I will provide. Thank you!

1

1 Answer 1

13

The snippet:

this.purchasedImages = data.images;

Leads us to believe data.images is an array of objects that do not have the download_progress property. So Vue can't detect/react when it changes.

To fix that, you can use Vue.set:

Vue.set(self.purchasedImages[key], 'download_progress', 0);

This is very well explained in Vue.js docs.


Another option: add the property before assigning to data

Just for completeness, you could also add the download_progress before assigning the array to the data property. This would allow Vue to notice it and be able to react to it.

Example:

let data = response.data;
this.purchasedImages = data.images.map(i => ({...i, download_progress: 0}));

if (this.purchasedImages.length > 0) {
    this.purchasedImagesVisible = true;
    // no need to set download_progress here as it was already set above
}

// if above could also be simplified to just:
this.purchasedImagesVisible = this.purchasedImages.length;




On a side note, since it is gonna be an array and not an object, I suggest you declare it as such:

data() {
    return {
        purchasedImages: [], // was: {},

This will have no effect, since you overwrite purchasedImages completely in (this.purchasedImages = data.images;), but it is a good practice as it documents that property's type.

Sign up to request clarification or add additional context in comments.

1 Comment

Ah now I see it! Thank you for your explanation!

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.