DEV Community

Cover image for Bag 1 - Upload with XHR & VueJS
IRPAN KUSUMA W
IRPAN KUSUMA W

Posted on • Edited on

Bag 1 - Upload with XHR & VueJS

[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

img-drop.vue

<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>
Enter fullscreen mode Exit fullscreen mode

kemudian untuk memanggil file tersebut seperti berikut ini

Home.vue

<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>
Enter fullscreen mode Exit fullscreen mode

kurang lebih tampilannya akan seperti berikut ini

  1. kolom untuk di drop
    Image description

  2. kolom ketika sudah ada image yang akan di upload
    Image description

Untuk API bisa cek disini ya https://dev.to/irpankusuma/bag-2-upload-with-expressjs-api-2lbi

Top comments (0)