pqina / vue-filepond

🔌 A handy FilePond adapter component for Vue
https://pqina.nl/filepond
MIT License
1.94k stars 128 forks source link

Custom server config example #15

Closed JoaquimLey closed 6 years ago

JoaquimLey commented 6 years ago

Expanding on #14 I would like to know if it is possible to have our own custom methods to handle server callbacks, I'm using Firebase as a DB/Backend and although it is abstracted, I'm using their own implementation, to make it a little more simple to understand, I want to upload a photo to an album, and this is the flow:

// This dude makes the actual upload  // Firebase abstraction
FirebaseController <-------------------- DatabaseManager <---- AlbumsRepository <--- UI-component

Reading the advanced server configuration it looks like this is possible, and this issue on the filepond repo aims to achieve the same result.

But whenever I try to this.$refs.pond.setOptions({ (...) }) I get an error saying setOptions is not a function.


TL;DR:

I would like to know if it is possible to add an example for the Vue counterpart of how to setup a custom server configuration, from component init, to assinging custom methods.

I also have a WIP app up if you want to test

https://photobook-1337.firebaseapp.com

Login with Google or Twitter -> New album -> Set name -> Create -> Upload photo(s)

If you open the console you can see that I'm firing the upload event to the repository when the file is added to the filePond component, but the UI doesn't reflect that :(


My code:

Template

<template>
  <div id="photo-file-upload">

    <file-pond
        name="filepond"
        ref="pond"
        label-idle="Click or Drop files here..."
        allow-multiple="true"
        accepted-file-types="image/jpeg, image/png"
        allowRevert="false"
        v-bind:files="myFiles"
        v-on:init="handleFilePondInit"
        v-on:warning="handleWarning"
        v-on:error="handleError"
        v-on:addfile="handleFileAdded"
        v-on:processfilestart="handleFileProcessStart"
        v-on:processfileprogress="handleFileProcessProgress"
        v-on:processfileabort="handleFileProcessAbort"
        v-on:processfileundo="handleFileProcessUndo"
        v-on:processfile="handleFileProcess"
        v-on:onremovefile="handleFileRemoved"/>
  </div>
</template>

All those methods are only printing to the console so I know what's happening at runtime.

Component init

<script>

(...)

// Import Vue FilePond
import vueFilePond from "vue-filepond";

// Import FilePond styles
import "filepond/dist/filepond.min.css";

// Import file type validation plugin
import FilePondPluginFileValidateType from "filepond-plugin-file-validate-type/dist/filepond-plugin-file-validate-type.esm.js";

// Import ImagePreview
import FilePondPluginImagePreview from "filepond-plugin-image-preview/dist/filepond-plugin-image-preview.esm.js";
import "filepond-plugin-image-preview/dist/filepond-plugin-image-preview.css";

// Create component
const FilePond = vueFilePond(
  FilePondPluginFileValidateType
  // FilePondPluginImagePreview
);

I've also tried to set the options to this FilePond const but with no luck

The magic

What I would to do:

methods: {
  (...)

  handleFilePondInit() {
     console.log("FilePond has initialized");
     this.$refs.pond.setOptions(
          {
           server: 
             {
                load: null,
                (...) // Other config things
                process: this.customProcessMethod
             }
          })
   },
},

In a perfect world I can handle all the things I need inside that process method, that has all those nice callbacks.

See also: https://github.com/pqina/filepond/issues/31 Basically the same issue, I just have an abstraction layer on top, I intend to build my own backend after I PoC with Firebase, and sadly the solution wasn't posted 😛

Once again, thanks for the time ! 🎉

Disclaimer: This might be a very stupid issue, but it is the first time I'm doing any dynamic web stuff, so JS, Vue are all new to me.

jamesblasco commented 6 years ago

Sorry @JoaquimLey I quite new here I i don´t know how to put colors in code. ( I got it 😛)

This is what I have been doing so far and for the time being it is working.

<template>
    <div>
        <p :class="{'red--text':rule}">{{label}} {{required ? ' *': ''}}</p>
        <FilePond
                name="file"
                ref="input"
                :maxFiles="max || 1"
                :allowMultiple="allowMultiple"
                allowFileTypeValidation="true"
                :acceptedFileTypes="type"
                :server="{  process, revert,  restore, load, fetch }"
                :files="files2"
                @init="handleFilePondInit"
                labelTapToCancel="Toca para cancelar"
                labelButtonAbortItemLoad = "Cancelar descarga"
                labelButtonRemoveItem = "Borrar archivo"
                labelButtonRetryItemLoad="Cargando"
                labelButtonRetryItemProcessing ="Reintentar"
                labelButtonUndoItemProcessing ="Deshacer"
                labelDecimalSeparator =","
                labelFileAdded = "Archivo Añadido"
                labelFileCountPlural ="Archivos"
                labelFileCountSingular ="Archivo"
                labelFileLoadError ="Error al subir"
                labelFileLoading ="Cargando"
                labelFileProcessing ="Subiendo"
                labelFileProcessingAborted ="Subida cancelada"
                labelFileProcessingComplete ="Subida completa"
                labelFileProcessingError ="Error al subir archivo"
                labelFileRemoved="Archivo eliminado"
                labelFileSizeNotAvailable ="Tamaño no disponible"
                labelFileWaitingForSize ="Comprobando tamaño"
                labelIdle ="Arrastre un archivo o pulse aqui"
                labelTapToRetry="Toca para reintentar"
                labelTapToUndo="Toca para deshacer"
                labelThousandsSeparator=""

        />
        <p v-if="rule" class="caption red--text">Archivo requerido</p>

    </div>
</template>

<script>
    import FilePond, { registerPlugin } from 'vue-filepond';

    // Import FilePond styles
    import 'filepond/dist/filepond.min.css';

    export default {
        name: "file-input",
        props: ['label', 'required', 'max', 'type'],
        data: function() {
            return {
                first: true,
                files: [], // files uploaded, input
                files2: [], // all files 
            };
        },
        computed: {
            allowMultiple () {
                return this.max !== 1
            },
            rule () {
                return this.required && !this.first && this.files.length === 0
            }
        },
        methods: {
            process  (fieldName, file, metadata, load, error, progress, abort)  {
                var self = this
                try {
                    progress(true, 0, 1024);
                    var uploadTask = this.$storage.ref().child('images/' + file.name).put(file, metadata)
                    uploadTask.on(this.$firebase.storage.TaskEvent.STATE_CHANGED,
                        function (snapshot) { progress(true, snapshot.bytesTransferred, snapshot.totalBytes)},
                        function (e) {  self.handleError(error, e)},
                        function () {
                            load(uploadTask.snapshot.ref.fullPath)
                            self.files.push(uploadTask.snapshot.ref.fullPath)
                        }
                    )
                    return {
                        abort: () => {
                            abort()
                            uploadTask.cancel();
                        }
                    }
                } catch (e) {
                    this.handleError(error, e)
                    return {
                        abort: () => { abort() }
                    }
                }
            },
            revert (uniqueFileId, load, error) {
                var self = this
                // Create a reference to the file to delete
                console.log(uniqueFileId)
                var desertRef = this.$storage.ref().child(uniqueFileId);
                        desertRef.delete().then(function() {
                            var index = self.files.indexOf(uniqueFileId);
                            if (index > -1) {
                                self.files.splice(index, 1);
                            }
                            load();
                        }).catch(function(e) {
                           this.handleError(error, e)
                        });
            },
            load (uniqueFileId, load, error){ error()},
            fetch (url, load, error, progress, abort, headers) {   error("Solo archivos locales")   },
            restore (uniqueFileId, load, error, progress, abort, headers) { error() },
            handleError (error, e){
                switch (e.code) {
                    case 'storage/canceled':
                        break;
                    default: error(e.message)
                }
            },
            handleFilePondInit: function() {
                this.$refs.input.getFiles();
            }
        },
        watch:{
            files : {
                handler: function (val, oldVal) {
                    if(this.first) this.first = false;
                    this.$emit('input', val)
                },
                deep: true
            }
        },
        components: {
            FilePond
        }
    }
</script>

And i call it like this

<file-input
                v-else-if="input.type === 'file'"
                :label="input.hint"
                v-model="input.value"
                :rules="rules(input.value, input.rules)"
                :required="required(input.rules)"
                :max="input.max">
</file-input>
JoaquimLey commented 6 years ago

Thanks, @jamesblasco I will look into this later today and get back to it!

JoaquimLey commented 6 years ago

Hello @jamesblasco, Thank you so much for giving this awesome example! I copy pasted most of the code and adapted, and it does work 🎉

But now I have a new issue, it only works once 🤔, after the first upload, for some reason when I add more items, nothing happens :(

Have you encountered this issue?

EDIT

@rikschennink maybe you have an idea? 🕺

Current setup


<template>
  <div id="photo-file-upload">

    <file-pond
        name="filepond"
        ref="pond"
        label-idle="Click or Drop files here..."
        allow-multiple="true"
        allow-drop="true"
        accepted-file-types="image/jpeg, image/png"
        :server="{  process, revert,  restore, load, fetch }"
        :init="handleFilePondInit"
        :warning="handleWarning"
        :error="handleError"
        :addfile="handleFileAdded"
        :processfilestart="handleFileProcessStart"
        :processfileprogress="handleFileProcessProgress"
        :processfileabort="handleFileProcessAbort"
        :processfileundo="handleFileProcessUndo"
        :processfile="handleFileProcess"
        :onremovefile="handleFileRemoved"/>
  </div>
</template>

And the methods


methods: {
    handleFilePondInit() {
      console.log("FilePond has initialized");
      // this.$refs.pond.getFiles();
    },

    process(fieldName, file, metadata, load, error, progress, abort) {
      console.log("process start");
      var self = this;
      try {
        progress(true, 0, 1);
        AlbumsRepository.uploadPhotoToAlbum(
          this.albumId,
          file,
          metadata,
          progressCallback => {
            progress(true, progressCallback, 1);
            console.log("Progress ", progressCallback);
          },
          status => {
            // console.log("Status ", status);
          },
          url => {
            load(url);
            console.log("Upload complete at ", url);
          },
          errorCallback => {
            console.log("Upload error ", error);
            self.handleError(errorCallback);
          }
        );
        return {
          abort: () => {
            abort();
            // uploadTask.cancel();
          }
        };
      } catch (e) {
        this.handleError(error, e);
        return {
          abort: () => {
            abort();
          }
        };
      }
    },

    load(uniqueFileId, load, error) {
      // error();
    },

    fetch(url, load, error, progress, abort, headers) {
      error("Local files only");
    },

    restore(uniqueFileId, load, error, progress, abort, headers) {
      // error();
    },

    revert(uniqueFileId, load, error) {
      // TODO -> AlbumRepository delete photo
      load();
    },

    // func (error[, file, status])
    handleWarning(error) {
      console.log("Warning file ", file);
    },

    // func (error[, file, status])
    handleError(error, e) {
      console.log("Catch error ", e.message);
      switch (e.code) {
        case "storage/canceled":
          break;
        default:
          error(e.message);
      }
    },
rikschennink commented 6 years ago

@JoaquimLey If I understand correctly when adding multiple files, only one of them will upload?

Can't say I spot something odd right away. I assume no errors are thrown?

Is the progressCallback going from 0 to 1? Or is it a function? That would be weird, but I'm just checking :-)

progress(true, progressCallback, 1);
JoaquimLey commented 6 years ago

Yes, let's say I add 5 files at the same time, the five files are uploaded.

But after the user drops one or more files it doesn't do anything else, unless I remove the files from the filepond then it "resumes" upload the other files.

Regarding the progress, I already pass the computed size e.g: progress(true, 0.28, 1)

The progressCallback gets this var:

var progress = (snapshot.bytesTransferred / snapshot.totalBytes) * 100;

Maybe I should remove that * 100 🤔 ?

rikschennink commented 6 years ago

@JoaquimLey I guess that makes it a value from 0 to 100, not sure if it will fix it but I think the * 100 can be removed.

JoaquimLey commented 6 years ago

Hi there @rikschennink, thanks for taking the time

I've removed the * 100

From the DatabaseManager my progressCallback is called like this:

var progress = (snapshot.bytesTransferred / snapshot.totalBytes);
progressCallback(progress)

And ont he ui component:


process(fieldName, file, metadata, load, error, progress, abort) {
  var self = this;
  try {
    AlbumsRepository.uploadPhotoToAlbum(
          this.albumId,
          file,
          metadata,
          progressCallback => {
            progress(true, progressCallback, 100);
            console.log("Progress ", progressCallback);
          },        
         (...)
   }
}

Looking at the UI and the logs, everything seems fine on the progress value side, goes from 0 to 100 value-wise and correctly reflects the %.

But the issue still remains the same, is this something related to the component's :files? I'm not adding or removing anything related to this :files, something I noticed @jamesblasco is using 🤔

Here's the whole component:

<template>
  <div id="photo-file-upload">

    <file-pond
        name="filepond"
        ref="pond"
        label-idle="Click or Drop files here..."
        allow-multiple="true"
        allow-drop="true"
        accepted-file-types="image/jpeg, image/png"
        :server="{  process, revert,  restore, load, fetch }"
        :init="handleFilePondInit"
        :warning="handleWarning"
        :error="handleError"
        :addfile="handleFileAdded"
        :processfilestart="handleFileProcessStart"
        :processfileprogress="handleFileProcessProgress"
        :processfileabort="handleFileProcessAbort"
        :processfileundo="handleFileProcessUndo"
        :processfile="handleFileProcess"
        :onremovefile="handleFileRemoved"/>
  </div>
</template>

<script>
// More config for FilePond
// https://pqina.nl/filepond/docs/patterns/plugins/introduction/

// Import Vue FilePond
import vueFilePond from "vue-filepond";

// Import FilePond styles
import "filepond/dist/filepond.min.css";

// Import file type validation plugin
import FilePondPluginFileValidateType from "filepond-plugin-file-validate-type/dist/filepond-plugin-file-validate-type.esm.js";

// Import ImagePreview
import FilePondPluginImagePreview from "filepond-plugin-image-preview/dist/filepond-plugin-image-preview.esm.js";
import "filepond-plugin-image-preview/dist/filepond-plugin-image-preview.css";

// Create component
const FilePond = vueFilePond(
  FilePondPluginFileValidateType
  // FilePondPluginImagePreview
);

import AlbumsRepository from "../repositories/AlbumsRepository";

export default {
  name: "PhotoFileUpload",
  components: {
    FilePond
  },
  props: {
    albumId: {
      type: String,
      required: true
    }
  },

  data() {
    return {
      files: [],
      allFiles: []
    };
  },

  computed: {
    currentAlbumId() {
      return this.albumId;
    }
  },
  watch: {
  },
  methods: {
    handleFilePondInit() {
      console.log("FilePond has initialized");
      // this.$refs.pond.getFiles();
    },

    process(fieldName, file, metadata, load, error, progress, abort) {
      console.log("process start");
      var self = this;
      try {
        AlbumsRepository.uploadPhotoToAlbum(
          this.albumId,
          file,
          metadata,
          progressCallback => {
            progress(true, progressCallback, 100);
            console.log("Progress ", progressCallback);
          },
          status => {
            // console.log("Status ", status);
          },
          url => {
            load(url);
            console.log("Upload complete at ", url);
          },
          errorCallback => {
            self.handleError(errorCallback);
            console.log("Upload error ", errorCallback);
          }
        );
        return {
          abort: () => {
            abort();
            // uploadTask.cancel();
          }
        };
      } catch (e) {
        this.handleError(error, e);
        console.log("Catched error ", e);
        return {
          abort: () => {
            abort();
          }
        };
      }
    },

    load(uniqueFileId, load, error) {
      // error();
    },

    fetch(url, load, error, progress, abort, headers) {
      error("Local files only");
    },

    restore(uniqueFileId, load, error, progress, abort, headers) {
      // error();
    },

    revert(uniqueFileId, load, error) {
      // TODO -> AlbumRepository delete photo
      // load();
    },

// all the handleMethods() that only console.log('stuff')
(...)
}
jamesblasco commented 6 years ago

Hi, yes, indeed the same problem happens to me. Unfortunately I can not spend time these next days I'm sorry. As soon as I can, I'll take a look

rikschennink commented 6 years ago

As I now have the complete code I’ll run some tests on Monday.

On 12 May 2018, 00:10 +0200, jamesblasco notifications@github.com, wrote:

Hi, yes, indeed the same problem happens to me. Unfortunately I can not spend time these next days I'm sorry. As soon as I can, I'll take a look — You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub, or mute the thread.

JoaquimLey commented 6 years ago

Looking forward to your help, but I would like to thank you both for taking the time and effort to help me out, @jamesblasco @rikschennink 👏🙏.

To clear up, yes there are no errors being thrown, I've deployed the latest code with the logs here:

There is only the ability to upload photos, so "removing" them from the component is just a UI :)

rikschennink commented 6 years ago

@JoaquimLey @jamesblasco Alright, I know what's happening but I don't know why it's happening.

FilePond will wait with file uploads till there are no more animations running ( this to ensure heavy processing of files, resizing, cropping, does not block animations ). Somehow it thinks that animations are still active and therefore it will not pick-up the newly dropped files. When the finished file is removed it can somehow reach idle state and it starts processing the new files.

Will see if I can reproduce this in the vanilla JS version ( I suspect it's not Vue related ).

rikschennink commented 6 years ago

@JoaquimLey These event listeners should be prepended with @ or v-on: instead of :, I'm not sure if it makes a difference ( it doesn't in my test environment, but the generated HTML looks a bit weird when using : ).

<file-pond
        @init="handleFilePondInit"
        @warning="handleWarning"
        @error="handleError"
        @addfile="handleFileAdded"
        @processfilestart="handleFileProcessStart"
        @processfileprogress="handleFileProcessProgress"
        @processfileabort="handleFileProcessAbort"
        @processfileundo="handleFileProcessUndo"
        @processfile="handleFileProcess"
        @removefile="handleFileRemoved"
</file-pond>
rikschennink commented 6 years ago

@JoaquimLey Can you remove the try-catch so we can see if there's an error being thrown?

rikschennink commented 6 years ago

@JoaquimLey @jamesblasco Quick update. I've managed to reproduce the problem on my local dev environment. It has to do with the progress callback.

rikschennink commented 6 years ago

@JoaquimLey @jamesblasco Turns out something was up with the logic that detected the idle state, working on a fix.

rikschennink commented 6 years ago

Just published version 1.4.1 of FilePond, should fix the issue.

JoaquimLey commented 6 years ago

@rikschennink seriously, I really appreciate the effort you've been putting into maintaining the component.

Once I launch this website you can expect some support from me 100% 👍

Will pull the latest version and check it works, I'm glad that somehow it help catch a bug ;), also good suggestion, should remove that try{} and force an error to be thrown

rikschennink commented 6 years ago

No problem @JoaquimLey ! I'm committed to making FilePond an amazingly flexible and robust file upload component, so I'm happy you choose to use it in your project even though it's a relatively young project.

JoaquimLey commented 6 years ago

Made it work! Thanks, @rikschennink I'm sure happy with your component, just testing if this will scale since I want so much custom stuff from it.

xcss2rq

Now I only have some small issues, like custom error messages showing up etc. but I think we a little bit of digging I can get there :) Thank you once again for your commitement and time! 💪

rikschennink commented 6 years ago

Fantastic! Glad to hear that @JoaquimLey 🚀

rikschennink commented 6 years ago

As the original issue has been resolved I'll close it. Do let me know if I can be of further assistance!

amjadkhan896 commented 5 years ago

Hi i am using vue filepond with laravel. I have one question. How i can show the uploaded images(form the database) in the filepond (Edit Case). Any Help will be appreciated

rikschennink commented 5 years ago

@amjadkhan896 I'm not sure how this question is related to the original issue.

See this entry in the docs on setting initial files: https://pqina.nl/filepond/docs/patterns/api/filepond-object/#setting-initial-files