0

I have a generic avatar preview modal:

<avatar-update :img-file="avatarFile" :show="avatarModalShow" :img-url="url" @close="avatarModalShow = !avatarModalShow" :change-avatar="updateCrop" @destroyUrl="imgUrl = null"> </avatar-update>

When an avatar is submitted I use my root to send a bunch of properties to the AvatarUpdate component.

HTML

<div>
    <label for="avatar" class="cursor-pointer thumbnail-link bg-white p-1 rounded-lg" href="">
        <img class="thumbnail" src="{{asset('images/avatars/avatar-1.png')}}">
    </label>
    <input id="avatar" class="hidden" type="file" @change="onFileChange"/>
</div>

Root

onFileChange: function(e) {
    const file = e.target.files[0];
    this.url = URL.createObjectURL(file);
    this.updateCrop = !this.updateCrop;
    this.avatarModalShow = !this.avatarModalShow;
    this.avatarFile = file;
},

When I console.log the file const in the onFileChange function I get the file object. However, when I try and output the {{imgFile}} property in the AvatarUpdate component I get an empty object.

I'm wondering if this is safe and if the file data can be manipulated between the root and AvatarUpdate component? Also is there something preventing me from being able to send and output the file object as a property? Why is it giving me an empty object on the AvatarUpdate component?

I'm sorry for so many questions, but my reasoning for including them into one post is that I think there could be some security functionality preventing me from sending the file object through a component.

Edit

Here is my AvatarUpload component:

<modal v-show="show" heading="Avatar Preview" @close="close">
  <div class="flex flex-col">

    <h4 class="text-blue-light mb-5">The avatar will be automatically cropped from the center.</h4>

      <div class="flex flex-col items-center">
          <img class="w-2/3" :src="imgUrl">
      </div>

      <p>{{imgFile}}</p>

      <button class="mt-4 h-10 self-end text-center bg-third-color hover:bg-secondary-color text-white font-bold py-2 px-4 rounded" v-on:click="submitAvatar()">Submit</button>
    </div>

<script>

    export default {
        props: ['show','imgUrl','changeAvatar','imgFile'],
        data() {
          return {
            image: null,
            message: null
          }
        },
        methods: {
            close: function(){
              this.$emit('close');
            },

            submitAvatar: function(){
              console.log(file);
              axios({
                  method: 'POST',
                  url: '/profile/avatar',
                  data: {},
              }).then(function (response) {


              this.message = "Your avatar has been submitted";   

              }.bind(this))
              .catch(function (error) {
                  console.log(error);
              });
            }
        }
    }
</script>

I already am able to obtain the blob from this.url = URL.createObjectURL(file); in the onFileChange function on the root instance. What I am trying to do is send the whole file object to the AvatarUpdate component using the :img-file="avatarFile" prop.

This way I can send the data in a way that can be accessed on the request in a Laravel controller:

submitAvatar: function(){
  //Change here!
  var data = new FormData()
    var file = this.imgFile;
    data.append('avatar', file);

  axios({
      method: 'POST',
      url: '/profile/avatar',
      data: data,
  }).then(function (response) {


  this.message = "Your avatar has been submitted";   

  }.bind(this))
  .catch(function (error) {
      console.log(error);
  });
}

Laravel UserController

UserController

public function avatar(Request $request)
{
    return $request->hasFile('avatar');
}
user3325126
  • 964
  • 3
  • 10
  • 29

1 Answers1

1

In your codes, this.avatarFile = file is one File(inherit from Blob) object, it can't be used in image src directly (if open the browser inspector, the value of img:src is [object File], Obviously the value is not what you expected).

You can use Javascript MDN: FileReader.readAsDataURL to reach the goal.

Javascript MDN: URL.createObjectURL() is another solution, but you have to handle the memory management carefully. Check Javascript MDN: URL.createObjectURL() Usage notes

PS: I recommend to convert the File object to (data-url or object-url) first then pass (data-url or object-url) to the child component. directly pass File object may meet reactivity issue.

One simple demo which uses FileReader:

Vue.config.productionTip = false
Vue.component('img-preview', {
  template: `<div>
              <img :src="imageBlob" alt="test"/>
             </div>`,
  props: ['imageBlob']
})
new Vue({
  el: '#app',
  data() {
    return {
      imageObj: null
    }
  },
  methods:{
    onFileChange: function(ev) {
      const selectFile = ev.target.files[0]
      let reader  = new FileReader()
      reader.readAsDataURL(selectFile)
      reader.addEventListener('load', () => {
        this.imageObj = reader.result
        //console.log('select image', reader.result)
      }, false)  
    },
  }
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.16/vue.js"></script>
<div id="app">
  <div>
      <label for="avatar">
          <img-preview :image-blob="imageObj"></img-preview>
      </label>
      <input id="avatar" class="hidden" type="file" @change="onFileChange($event)"/>
  </div>
</div>

One simple demo which uses createObjectURL:

Vue.config.productionTip = false
Vue.component('img-preview', {
  template: `<div>
              <img :src="imageBlob" alt="test"/>
             </div>`,
  props: ['imageBlob']
})
new Vue({
  el: '#app',
  data() {
    return {
      imageObj: null
    }
  },
  methods:{
    onFileChange: function(ev) {
      const selectFile = ev.target.files[0]
      this.imageObj = URL.createObjectURL(selectFile)
    },
  }
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.16/vue.js"></script>
<div id="app">
  <div>
      <label for="avatar">
          <img-preview :image-blob="imageObj"></img-preview>
      </label>
      <input id="avatar" class="hidden" type="file" @change="onFileChange($event)"/>
  </div>
</div>

Below is one demo (directly pass File Object to child component):

Vue.config.productionTip = false
Vue.component('img-preview', {
  template: `<div>{{imageBlob}}
              <img :src="internalImageObj" alt="test"/>
             </div>`,
  props: ['imageBlob'],
  data() {
    return {
      internalImageObj: ''
    }
  },
  watch: {
    imageBlob: function (newVal) {
      let reader  = new FileReader()
      reader.readAsDataURL(newVal)
      reader.addEventListener('load', () => {
        this.internalImageObj = reader.result
      }, false)  
    }
  }
})
new Vue({
  el: '#app',
  data() {
    return {
      selectedFile: null
    }
  },
  methods:{
    onFileChange: function(ev) {
      this.selectedFile = ev.target.files[0]
    },
  }
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.16/vue.js"></script>
<div id="app">
  <div>
      <label for="avatar">
          <img-preview :image-blob="selectedFile"></img-preview>
      </label>
      <input id="avatar" class="hidden" type="file" @change="onFileChange($event)"/>
  </div>
</div>
Sphinx
  • 9,326
  • 2
  • 21
  • 40
  • Hi thanks for your answer. One of my concerns is validating the file as an image on the backend. I was going to use `this.avatarFile` to try and return the other data such as the file name and type and rid myself of passing the blob as a property. I was generally worried that someone could change the type prop and that would make it past backend validation. Might be a dumb worry though. – user3325126 Aug 30 '18 at 01:05
  • Not sure what is your goal. probably check [https://stackoverflow.com/questions/29805909/jquery-how-to-check-if-uploaded-file-is-an-image-without-checking-extensions](https://stackoverflow.com/questions/29805909/jquery-how-to-check-if-uploaded-file-is-an-image-without-checking-extensions), then you can define other props which pass file name or else. – Sphinx Aug 30 '18 at 01:07
  • I made an edit. Hopefully it better explains what I'm trying to accomplish. Thank you for your help so far. – user3325126 Aug 30 '18 at 04:04
  • @user3325126, the third demo shows how to pass the File Object to the component. You can add your `submitAvatar` into that demo then try again. – Sphinx Aug 30 '18 at 16:05
  • Thanks for your patients. – user3325126 Aug 30 '18 at 23:06
  • Just another question there are no security concerns with this? I don't know why I believe this. I might have a faulty understanding of how Laravel image validation works, but I think my concern is just the file type being changed and making it through validation to save a corrupt file. – user3325126 Aug 30 '18 at 23:09
  • @user3325126, [this answer](https://stackoverflow.com/a/29806920/5665870) already show how to validate one file is image or else. – Sphinx Aug 30 '18 at 23:13