File upload with Aurelia

Update

This post is no longer up to date, as you can now bind to the files attribute of your input element:

<input type="file" files.bind="files" />

This would bind to the files property of your viewmodel.

This was the original post:

I'm getting up to steam with Aurelia and really like it. One thing that is missing, though, is one-way databinding from view to viewmodel. There is a GitHub issue for it, so I suspect this will be included at some time. But for now, you need to work around this limitation.

This is a problem when working with <input type="file"> controls. The property you need to get the file is the files property, but this is a readonly property. Having Aurelia bind to this property via files.bind='viewModelProperty' will generate an error stating: "setting a property that has only a getter".

So here's a workaround. It's not very elegant, so if you know of something better, please let me know (I don't have comments on this blog yet, but drop me a line via Twitter).

First, tell the input to call a method on your viewmodel when a file has been selected, and pass in the event ($event):

<input type="file" change.delegate="fileSelected($event)"></input>

The method that is called will just store the selected file in a property of the viewmodel:

fileSelected() {
    let reader = new FileReader();
    let file = this.$event.target.files[0];
    reader.readAsDataURL(file);
    this.fileName = file.name;
    reader.onload = () => {
        this.file = reader.result;
    };
}

Let's break that down:

let reader = new FileReader();
let file = this.$event.target.files[0];
reader.readAsDataURL(file);

This uses the new FileReader interface to read out the file that we received from the change event of our <input> element. We also store the filename in our viewmodel.

this.fileName = file.name;
reader.onload = () => {
    this.file = reader.result;
};

Because the FileReader works asynchronously, we add a handler to the onload event and store the result of the read in the file property. (Thanks to Kamal Bhatt for pointing me to the arrow function technique, which is new in ES6).

You can now put the value of this file property in a JSON object and post it to the server. It will be a Base64 string that you can get the bytes of by doing something like:

var startIndex = settingsDto.Logo.IndexOf(",") + 1;
var base64String = settingsDto.Logo.Substring(startIndex);
var imageBytes = Convert.FromBase64String(base64String);
var imageExtension = settingsDto.LogoName.Substring(settingsDto.LogoName.LastIndexOf("."));