23

Given the following code:

<input data-bind="event: { change: uploadImage(this.files[0]) }" style="width: 10px;" type="file">

I get an error saying "files is not defined". I'm trying to convert this demo:

https://github.com/paulrouget/miniuploader/blob/gh-pages/index.html

To a knockout-friendly implementation. The error happens when I load the page. Any idea how I can access the file, if it's been specified by the user?

jsfiddle : http://jsfiddle.net/LkqTU/9597/

SB2055
  • 12,272
  • 32
  • 97
  • 202

3 Answers3

45

You have two problems:

  • if you just write a function call (uploadImage(this.files[0])) inside an object literal it will be executed once when the object literal is initialized so when KO parses the binding. So it will be executed once with the wrong arguments and you change event won't work. You can make it work with wrapping it into an anonymous function. See in documentation Accessing the event object, or passing more parameters section.

  • the this doesn't refer to the current element in the binding you need to use $element instead.

So the correct binding looks like this:

data-bind="event: { change: function() { uploadImage($element.files[0]) } }"

Demo JSFiddle.

nemesv
  • 138,284
  • 16
  • 416
  • 359
21

For anyone interested, you could use the following custom binding, which allows binding a file input element to a knockout observable containing the File.

It handles setting the observable to the chosen file (as answered by @nemesv), as well as clearing the input element when the observable is being set to null (see this answer):

ko.bindingHandlers.fileUpload = {
    init: function (element, valueAccessor) {
        $(element).change(function () {
            valueAccessor()(element.files[0]);
        });
    },
    update: function (element, valueAccessor) {
        if (ko.unwrap(valueAccessor()) === null) {
            $(element).wrap('<form>').closest('form').get(0).reset();
            $(element).unwrap();
        }
    }
};

Example:

function Example() {
  var self = this;

  self.uploadFile = ko.observable(null);
  self.uploadName = ko.computed(function() {
    return !!self.uploadFile() ? self.uploadFile().name : '-';
  });

  self.clear = function() {
    self.uploadFile(null);
  };
};

ko.bindingHandlers.fileUpload = {
  init: function(element, valueAccessor) {
    $(element).change(function() {
      valueAccessor()(element.files[0]);
    });
  },
  update: function(element, valueAccessor) {
    if (ko.unwrap(valueAccessor()) === null) {
      $(element).wrap('<form>').closest('form').get(0).reset();
      $(element).unwrap();
    }
  }
};

ko.applyBindings(new Example());
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>

<input type="file" data-bind="fileUpload: uploadFile">
<br/>
<br/>Selected file name: <span data-bind="text: uploadName"></span>
<br/>
<button data-bind="click: clear">Clear input</button>
Community
  • 1
  • 1
Philip Bijker
  • 4,955
  • 2
  • 36
  • 44
0

Probably this is the simplest one, also you can see the preview of the image before upload

<input type="file" data-bind="fileSrc: src" id="file"/>
<img data-bind="attr:{src:src}"/>

$(function() {
    ko.applyBindings(new ViewModel());
});

var ViewModel = function () {
    var self = this;

    self.src = ko.observable();
};

ko.bindingHandlers.fileSrc = {
    init: function (element, valueAccessor) {
        ko.utils.registerEventHandler(element, "change", function () {
            var reader = new FileReader();

            reader.onload = function (e) {
                var value = valueAccessor();
                value(e.target.result);
            }

            reader.readAsDataURL(element.files[0]);
        });
    }
};
Distnie Manuel
  • 131
  • 1
  • 3