Creating a static site: large size file uploader by Firebase Hosting and Storage | Part.2

Posted: March 05, 2020

This article is a sequel article from Creating a static site: large size file uploader by Firebase Hosting and Storage | Part.1


6. Prototyping #3 - Upload data to Firebase Storage

Upload to Firebase Storage itself is straigtforward.

<!-- /* index.html */ -->

<!-- #02 -->
  <section id="did-login" class="container" style="display: none;">

    <div id="upload-form" class="upload-form">
        <div class="upload-button">
            <!-- ↓we need this tag↓ -->
            <input type="file" name="fileinput" id="fileinput" />
        </div>
    </div>
// app.js

...


const input = _('fileinput');
input.addEventListener('change', ev => {
  const file = ev.target.files[0]
  firebase.storage().ref('uploaded_items/'+file.name).put(file)
})

init()
})();

I click Choose file and upload a file named 20200305__evalphobia__awesome_photo2.png (Pic. 6-1).

After that I reload browser, then you can see the uploaded photos.

An image from NotionAn image from Notion

Now Listing and Uploading feature works correctly.

But wee need one more step for the confortable UI and UX.

7. Upload with progress bar

When yo upload large size file, you might think "when it'll be finished...?"

So we'll add progress bar and show the notification after finished.

Besides that we need some other missing pieces.

Below will be added in this step,

(a) File renaming

We need to change the upload logic in app.js

Write a main logic first,

const input = _('fileinput');
input.addEventListener("change", ev => {
  const f = ev.target.files;
  if (!f || f.length != 1) {
    return
  }
  uploadFile(f)
})


// main upload logic
function uploadFile(files) {
  const file = files[0]
  const fileName = _getFileName(file)

  const storageRef = firebase.storage().ref(storagePrefix);
  const uploadRef = storageRef.child(fileName);

  const blob = new Blob(files, { type: file.type });
  const uploadTask = uploadRef.put(blob) // file is uploading here!

  uploadTask.on('state_changed', snapshot => {
    // we will add the progress bar logic here!

  }, err => {
    // error occors when uploading...
    alert("Failed to upload the file")

  }, () => {
    // After the upload task...
    alert("Upload Complete!")
  })
}

And add helper functions,

function _getFileName(file) {
  // get user name
  const user = firebase.auth().currentUser;
  let userName = user.email.split('@')[0];
  if (!userName) {
    // Don't allow empty email address user.
    alert("Upload failed...")
    location.reload()
    return
  }

  // if the file name already contains double underscore, change to single.
  const baseName = file.name.replace('__', '_')
  const fileName = `${_getYYYYMMDD(new Date())}__${userName}__${baseName}`
  return fileName
}


function _getYYYYMMDD(dt) {
  const yyyy = dt.getFullYear();
  const mm = ("00" + (dt.getMonth()+1)).slice(-2);
  const dd = ("00" + dt.getDate()).slice(-2);
  return `${yyyy}-${mm}-${dd}`
}

Check this step in your browser!

(b) Refresh uploaded file list after the upload task

To refresh the view, create a new function for finished upload and add it from just alert .

uploadTask.on('state_changed', snapshot => {
  // we will add the progress bar logic here!
}, err => {
  // error occors when uploading...
  completeProgress()
  alert("Failed to upload the file")
}, () => {
  // After the upload task...
  completeProgress()
  alert("Upload Complete!")
})


// clear input form and reload uploaded file list
function completeProgress() {
  _("fileinput").value = '';
  fetchFileListAndUpdateListView() // already implemented in [5. Prototyping #2]
}

That's it!

(b) Progress Bar

(pre) Last thing is a progress bar.

Add 2 tags, one is a visual bar and another is text like XY% .

<!-- #02 -->
  <section id="did-login" class="container" style="display: none;">

    <div id="upload-form" class="upload-form">
        <div class="upload-button">
            <input type="file" name="fileinput" id="fileinput" />
        </div>

        <!-- ↓ Add these two tags -->
        <progress id="progressBar" value="0" max="100" style="width:150px;"></progress>
        <p id="progressStatus"></p>
    </div>
function uploadFile(files) {
  
  ...

  uploadTask.on('state_changed', snapshot => {

    // !! changed here !!
    // uploading...
    const percent = (snapshot.bytesTransferred / snapshot.totalBytes) * 100;
    _("progressBar").value = Math.round(percent);
    _("progressStatus").innerHTML = Math.round(percent) + "% uploaded...";
    // !! changed here !!

  }, err => {
    // error occors when uploading...
    completeProgress()
    alert("Failed to upload the file")
  }, () => {
    // After the upload task...
    completeProgress()
    alert("Upload Complete!")
  })


// clear input form and reload uploaded file list
function completeProgress() {
  _("fileinput").value = '';
  _("progressBar").value = 0;
  _("progressStatus").innerHTML = ''
  fetchFileListAndUpdateListView() // already implemented in [5. Prototyping #2]
}

Then upload a large size file,

An image from Notion

Nice!

8. Custom upload button

The default upload button is too small and not easy to click, and of course it's not fancy!

So we'll change the style of the button.

<!-- #02 -->
<section id="did-login" class="container" style="display: none;">

    <div id="upload-form" class="upload-form">
        <div class="upload-button"> <!-- this is a visual button -->
            Choose file
            <input type="file" name="fileinput" id="fileinput" />
            <input type="text" id="filename" class="filename" disabled />
        </div>

        <br />

        <progress id="progressBar" class="progressbar" value="0" max="100"></progress>
        <p id="progressStatus" class="progress-status"></p>
    </div>
<style>
...

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

  // these are for progress
  .progressbar {
      width: 150px;
  }
  .progress-status {
      margin-top: 0px;
  }

...

  // these are for upload button
  .upload-button {
    display: inline-block;
    position: relative;
    overflow: hidden;
    border-radius: 3px;
    background: #3388cc;
    color: #ffffff;
    text-align: center;
    padding: 10px;
    line-height: 30px;
    width: 180px;
    cursor: pointer;
  }
  .upload-button:hover {
      background: #4499dd;
  }
  .upload-button input[type=file] {
      position: absolute;
      top: 0;
      left: 0;
      width: 100%;
      height: 100%;
      cursor: pointer;
      opacity: 0;
  }
  .filename {
      display: none;
      background: rgba(255,255,255,0.2);
      border-radius: 3px;
      padding: 3px;
      color: #ffffff;
  }

...
</style>

And we'll add helper functions for displaying file name in uploading.

// update display and refuse file upload
function displayFileName(name) {
  const el = _("filename")

  el.value = name
  el.style.display = 'inline-block';
  _("fileinput").disabled = true;
}

// clear display and accept file upload
function clearFileName(name) {
  const el = _("filename")

  el.value = ''
  el.style.display = 'none';
  _("fileinput").disabled = '';
}

Then use them in both of starting and finished upload,

function uploadFile(files) {
  const file = files[0]
  const fileName = getFileName(file)
  displayFileName(fileName)  // add this line


...


// clear input form and reload uploaded file list
function completeProgress() {
  _("fileinput").value = ''
  _("progressBar").value = 0
  _("progressStatus").innerHTML = ''
  fetchFileListAndUpdateListView()
  clearFileName()  // add this line
}

You can see the real world upload button now!

An image from NotionAn image from Notion

Whole codes are on gist