Bag 1 - Upload with XHR & VueJS
Tags: vue, webcomponents, beginners, javascript
Published: 2022-03-10 14:28:15

[Halo Sobat,]

Berikut cara upload file menggunakan XHR. Saya menggunakan framework vuejs untuk ini, saya harap kalian sudah paham menggunakan vuejs + vuetify. Dan berikut adalah sample codenya

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/z1ufek0lhdjd2cxozz0g.png)

img-drop.vue
```javascript
<template>
  <v-container>
    <div class="helper"></div>
    <div
      class="drop display-inline align-center"
      @dragover.prevent
      @drop="onDrop"
    >
      <div class="helper"></div>
      <label v-if="image.length == 0" class="btn display-inline">
        <div>SELECT OR DROP AN IMAGE</div>
        <div style="font-size: 8px; text-align: center">accepted image</div>
        <input
          type="file"
          :multiple="multiple"
          name="image"
          @change="onChange"
        />
      </label>

      <div
        class="hidden display-inline align-center"
        v-else
        v-bind:class="{ image: true }"
      >
        <div v-for="(k, v) in image" :key="v" class="flex">
          <v-img
            v-if="['.jpeg', '.jpg', '.png'].includes(getExt(k.savename))"
            :src="k.url"
            contain
            tile
            max-height="200"
            max-width="250"
            class="img"
          >
            <v-fade-transition>
              <v-overlay absolute color="#036358">
                <v-btn outlined x-small @click="print(k.url, k.savename)"
                  >DOWNLOAD</v-btn
                >
                <v-btn
                  class="ml-2"
                  outlined
                  color="red"
                  x-small
                  @click="removeFile(k)"
                  >DELETE</v-btn
                >
              </v-overlay>
            </v-fade-transition>
          </v-img>
          <div v-else style="max-height: 60px; max-width: 100px" class="img">
            <div>
              <v-btn outlined x-small @click="print(k.url, k.savename)"
                >DOWNLOAD</v-btn
              >
              <v-btn
                class="ml-2"
                outlined
                color="red"
                x-small
                @click="removeFile(k)"
                >DELETE</v-btn
              >
            </div>
          </div>
        </div>
      </div>
    </div>
  </v-container>
</template>

<script>
export default {
  name: "image-drop",
  props: {
    multiple: null,
    id: null,
  },
  data() {
    return {
      image: [],
      acceptMime: ["image/jpeg", "image/png"],
    };
  },
  methods: {
    onDrop: function (e) {
      e.stopPropagation();
      e.preventDefault();
      var files = e.dataTransfer.files;

      if (!this.multiple && files.length > 1) {
        console.log(`Tidak dapat meng-upload lebih dari 1 file sekaligus.`);
        return;
      }
      this.onPicked(files);
    },
    onPicked(files) {
      let image = [];
      if (files.length > 0) {
        for (let i = 0; i < files.length; i++) {
          let x = files[i].name;
          if (x.lastIndexOf(".") <= 0) {
            return;
          }

          let type = files[i].type;
          let valid = this.acceptMime.indexOf(type) !== -1;
          if (!valid) {
            console.log(`jenis ekstensi tidak didukung`);
            return false;
          }

          const fr = new FileReader();
          fr.readAsDataURL(files[i]);
          fr.addEventListener("load", () => {
            image.push({
              id: i + 1,
              uid: this.id,
              size: Math.round(files[i].size / 1000),
              isAllow: files[i].size / 1024 / 1024 > 2 ? false : true,
              type: files[i].type,
              name: x,
              savename: this.buildName(x),
              url: fr.result,
              file: files[i],
              filename: this.buildName(x),
              mime_type: files[i].type,
              filesize: `${Math.round(files[i].size / 1000)} kB`,
            });
          });
        }
      }

      let interval;
      interval = setInterval(() => {
        if (image.length > 0) {
          this.$emit("value", image);
          this.image = image;
          clearInterval(interval);
        }
      }, 50);
    },
    onChange(e) {
      var files = e.target.files;
      this.onPicked(files);
    },
    removeFile(k) {
      if (k) {
        this.image = this.image.filter((e) => e.savename != k.savename);
        this.$emit("remove", k);
      }
    },
    // untuk moment() saya menggunakan library dayjs
    buildName(name) {
      const u = moment().format("DDMMYYYYHHmmss.SSS");
      const l = name.length;
      const i = name.lastIndexOf(".");
      const e = name.substring(i, l);
      name = name.substr(0, i);
      let newname = `${name}-${u}`;
      let filename = newname;
      filename = filename.replace(
        /([~!@#$%^&*()_+=.`{}\[\]\|\\:;'<>,\/? ])+/g,
        "-"
      );

      return filename + e;
    },
  },
};
</script>

<style scoped>
*,
*:after,
*:before {
  -webkit-box-sizing: border-box;
  -moz-box-sizing: border-box;
  box-sizing: border-box;
  -webkit-tap-highlight-color: rgba(0, 0, 0, 0);
  -webkit-touch-callout: none;
}

.flex {
  display: flex;
}

.btn {
  background-color: #f2f2f2;
  border: 0;
  color: black;
  cursor: pointer;
  display: inline-block;
  font-weight: bold;
  padding: 15px 35px;
  position: relative;
}

input[type="file"] {
  position: absolute;
  opacity: 0;
  z-index: -1;
}

.align-center {
  text-align: center;
}

.helper {
  height: 100%;
  display: inline-block;
  vertical-align: middle;
  width: 0;
}

.hidden {
  display: none !important;
}

.hidden.image {
  display: inline-block !important;
}

.display-inline {
  display: inline-block;
  vertical-align: middle;
}

.img {
  border: 1px solid #b71c1c;
  display: inline-block;
  height: auto;
  max-height: 80%;
  max-width: 80%;
  width: auto;
}

.drop {
  background-color: #f2f2f2;
  border: 4px dashed #ccc;
  background-color: #f2f2f2;
  border-radius: 2px;
  height: auto;
  max-height: 400px;
  max-width: 500px;
  width: 100%;
}

.drop:hover {
  border: 4px dashed #b71c1c;
}
</style>
```

kemudian untuk memanggil file tersebut seperti berikut ini

Home.vue
```javascript
<template>
  <v-container fluid fill-height>
    <v-row align="center" justify="center">
      <v-col sm="12" lg="12" md="12" xl="12" align-self="center">
        <image-drop :multiple="false" @value="onUpload"></image-drop>
      </v-col>
    </v-row>
  </v-container>
</template>

<script>
export default {
  components: {
    ImgDrop: () => import(`@/components/ImageDrop.vue`)
  },
  data() {
    return {};
  },
  methods: {
     onUpload(v){
        const file = v[0].file
        this.uploadFile(`http://yourAPI.com`, '/', file, (res) => {
           console.log(`response callback`, res)
        }
     },
     // path is target_path on your backend
     uploadFile: (url, path = "/", upload = null, callback) => {
      let xhr = new XMLHttpRequest();
      let fd = new FormData();

      fd.append("target_path", path);
      if (upload && Array.isArray(upload)) {
        for (let i = 0; i < upload.length; i++) {
          fd.append("upload[]", upload[i].file, upload[i].filename);
        }
      } else {
        fd.append("upload", upload.file, upload.filename);
      }

      console.log("fd", fd);

      xhr.open("POST", url, true);
      xhr.onprogress = function () {
        console.log(`loading start`);
      };
      xhr.onerror = function () {
        console.log(`loading end`);
        if (typeof callback === "function") {
          callback({
            status_code: "error",
            status_code: "error",
            status_message: "Terjadi masalah saat upload data.",
          });
        }
      };

      xhr.onload = function () {
        let res = xhr.response;
        xhr.readyState == 4 && xhr.status == 200
          ? console.log("Success.")
          : console.log("Failed.");
        if (typeof callback === "function") {
          callback(res);
        }
      };
      xhr.send(fd);
    },
  }
};
</script>
```

kurang lebih tampilannya akan seperti berikut ini
1. kolom untuk di drop
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/z1ufek0lhdjd2cxozz0g.png)

2. kolom ketika sudah ada image yang akan di upload
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/advh9gieayyrfpqt9nxu.png)
 
Untuk API bisa cek disini ya https://dev.to/irpankusuma/bag-2-upload-with-expressjs-api-2lbi