Creating a clickable area for uploading files

Creating a clickable area is not as easy as you would think. The open file window will only pop up when there is a click event on a file input element. I've created the following work-around for this.


<div id="clickable-area">
  <a id='fileOpener' />
  <input type="file" id="fileBtn" />

  <!-- Styling this is a pain. I'll discuss styling upload buttons in a later post -->
  <div class="button"><span>Upload</span></div>
  <p> a clickable zone </p>
</div>

//The file input element is the only element that can make an open file dialogue appear
//The following outlines how we get around this
//
//user clicks the clickable area > we send a click event
//to the file opener > the file opener clicks on the open
//file button > the open file dialogue pops up

function clickableAreaListener(e){
  let clickEvent = new CustomEvent("click",{"from":"fileOpenerHandler"});
  document.getElementById("fileOpener").dispatchEvent(clickEvent);
}
function fileOpenerHandler(e) {
  document.getElementById("fileBtn").click();
  e.preventDefault();
}

function fileSelectedListener(e){
    readInFiles(e.target.files);
}

document.getElementById('fileBtn').addEventListener('change', fileSelectedListener);
document.getElementById("clickable-area").addEventListener('click', clickableAreaListener);
document.getElementById("fileOpener").addEventListener("click", fileOpenerHandler);

Styling an upload button

If I'm making my own buttons out of divs, and I want to create a upload button, I'll run into a problem. The only way to make the open file window pop up is through a file input element, and the file input element creates a button that we have no way of accessing in CSS. After much research I've found that the following workaround.


<div id="clickable-area" class="inputWrapper control">
  <!-- My previous post explains what this fileOpener element is -->
  <a id='fileOpener'></a>
  <input type="file" class="fileInput" id="fileBtn" accept="image/*" multiple/>
    <div class="yellow-button"><span>Add Images</span>
    </div><p id="clickable-area-text"> or drag images here (not implemented) &nbsp;</p>
</div>


/*remove the display:none to get a better idea of what is going on here
I'm changing the font-size to create a huge button
Then I set display to none
There is then a big clickable button behind whatever custom button I create
Alternatively, On newer browser's they made it possible to access the
file input button through the &::-webkit-file-upload-button or &::-ms-browse psuedo-classes*/

.fileInput
  display: none
  cursor: pointer
  position: absolute
  top: 0
  right: 0
  font-size: 100px
  &::-webkit-file-upload-button, &::-ms-browse
    visibility: hidden
#clickable-area
  background: #ccc

#clickable-area-text
  display: inline
  padding: 30px

.yellow-button /*replace these styles with your own*/
  cursor: pointer
  color: white
  background: $button-color
  height: 30px
  width: 120px
  padding: 0
  font-size: 22px
  text-align: center
  text-shadow: 0 1px 2px rgba(0, 0, 0, 0.25)

Saving files in a Chrome App

I couldn't find any complete examples of anyone else doing this, and the documentation referred to a mysterious undocumented object. Google searches return too many examples of using local storage or of using the download API in a chrome extension. Chrome Apps are seperate from Chrome extenstions. There is no way of downloading a file like you might normally do in a website or Chrome Extension, and so this is the way of going about saving files in a Chrome App.


<div><a id="download" class="yellow-button">&nbsp; Download Images &nbsp;</a></div>

document.getElementById('download').addEventListener('click', download);

function download(data) {

  if (!data) throw 'nothing to download'

  if (chromeApp){
    chrome.fileSystem.chooseEntry({type:'openDirectory'}, function(entry) {
        chrome.fileSystem.getWritableEntry(entry, function(entry) {
          entry.getFile('example.txt', {create:true}, function(entry) {
            entry.createWriter(function(writer){
              writer.write(data, 'text/plain')) //My next post will change this to save images
            })
          })
        })
    })

  }else{
    let a = document.createElement("a");
    a.href = data;
    a.download = 'downloadExample.txt'
    document.body.appendChild(a)
    a.click();
    window.URL.revokeObjectURL(a.href);
    a.remove()
  }

Saving images in a Chrome App

This post builds upon the last post to add two peices of functionality. 1) this example shows saving multiple file while only prompting the user for a location once. 2) this example shows how to save an image that was created from a HTML5 canvas.


function download() {

    function b64toBlob(b64Data, contentType, sliceSize) {
        contentType = contentType || '';
        sliceSize = sliceSize || 512;

        var byteCharacters = atob(b64Data);
        var byteArrays = [];

        for (var offset = 0; offset < byteCharacters.length; offset += sliceSize) {
            var slice = byteCharacters.slice(offset, offset + sliceSize);

            var byteNumbers = new Array(slice.length);
            for (var i = 0; i < slice.length; i++) {
                byteNumbers[i] = slice.charCodeAt(i);
            }

            var byteArray = new Uint8Array(byteNumbers);

            byteArrays.push(byteArray);
        }

        var blob = new Blob(byteArrays, {type: contentType});
        return blob;
    }

    chrome.fileSystem.chooseEntry({type:'openDirectory'}, function(entry) {
      for (let myPicObj of getMyPics()){
        chrome.fileSystem.getWritableEntry(entry, function(entry) {
          entry.getFile(myPicObj.name, {create:true}, function(entry) {
            entry.createWriter(function(writer){
              writer.write(b64toBlob(myPicObj.data.slice(23), 'image/jpg'))
            })
          })
        })
      }
    })

  }

Putting it all together

The first example shows a basic example of uploading, compressing, and saving pics
example 1

The second example builds upon the first to incorporate everything talked about in the previous four posts
example 2

Undo history

Realizing the need for a good undo system, I created one for LIO.
My undo system is capable of supporting groups of undo history, so the undo function will first go through every item one by one in the current group, after which it will start skipping over groups of undo history. This gave me a way for allowing one to undo actions in the current image filter, after which entire filters will be undone.
Undo/redo history demo

Example Usage


  history = new History()
  history.add(0)
  history.newGroup()
  history.add(1)
  history.add(2)
  history.newGroup()
  history.add(3)
  history.add(4)

  And then undoing it produces the following result
  history.undo() //3
  history.undo() //2
  history.undo() //0 //The default behavior is to skip over previous entries in any group but the most current one.