user5567313
user5567313

Reputation:

Difference in event change in Internet Explore and Chrome

I have an website which uses an input type=file to upload some files.

After uploading I don't want to display the file selection, so I am going to delete it by calling event.target.value = null.

function viewModel() {
  var self = this;
  self.addAudioFile = function(data, event) {
    event.preventDefault();

    var context = ko.contextFor(event.target);
    var selectedFile = event.target.files[0];
    var targetPrompt = data.prompt;
    console.log("aa");
    event.target.value = null;

  }
}

$(document).ready(function() {
  var newModel = new viewModel();
  ko.applyBindings(newModel);
});
<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>

<div class="btn-link prompt-audio-control">
  <div class="btn-file">
    <input type="file" data-bind="event: {change: $root.addAudioFile}" accept="audio/wav|audio/pcm|audio|vox">
    <span> Upload </span>
  </div>
</div>

What confuses me here is when I used Chrome to test, the function addAudioFile is executed only one time, but when I used IE, it executed this function 2 times.

I used the console.log("aa") in oder to test it. Can anyone explain me why this happens?

Upvotes: 0

Views: 393

Answers (3)

Adrian
Adrian

Reputation: 1587

It was triggered twice due to this call.

event.target.value = null;

After that line, the change event is fired again. Try putting another console.log after that line so you can see the effect.

Something like:

event.target.value = null;
console.log("bb");

So in order to stop the function from continuing its second call you can trap the value of event.target.value.

self.addAudioFile = function(data, event) {
    if(event.target.value === ""){
        return;
    } 
    ... rest of code here ...
}

Why is it checked as a string and not a null? That part i'm not sure but the browsers engine are converting it into a blank string hence the condition.

And on the part where chrome doesn't fire twice, i'll just leave this to browser experts, but my guess is chrome engine is set to not change the file if a null value is given (just my wild guess though).

==================

And lastly, you can use knockout this way but you "should" not do it that way :D See Tomalak's answer on how to separate knockout's DOM concerns using custom bindings.

Upvotes: 1

What i undestand:

You want to "hide" the text about "what file you upload" after upload the file... (what redundant phrase lol), maybe this could help you instead "erasing" the selection.

Creating a HTML Button:

.fileUpload {
  position: relative;
  overflow: hidden;
  margin: 10px;
}
.fileUpload input.upload {
  position: absolute;
  top: 0;
  left: 0;
  margin: 0;
  padding: 0;
  font-size: 20px;
  cursor: pointer;
  opacity: 0;
  filter: alpha(opacity=0);
}
<div class="fileUpload btn btn-primary">
  <span>Upload</span>
  <input type="file" class="upload" />
</div>

Upvotes: 1

Tomalak
Tomalak

Reputation: 338326

As a matter of principle, whenever you find that you access DOM elements, events or even the binding context in a knockout viewmodel then you are doing something wrong.

The only place in a knockout application where you interact with the DOM is the binding handler. If there is no binding handler that does what you need, write one.

// exemplary binding handler ---------------------------------------------------------
ko.bindingHandlers.file = {
    init: function (element, valueAccessor) {
        if ( !ko.isWriteableObservable(valueAccessor()) ) return;

        // alternatively $(element).on("change", ...
        ko.utils.registerEventHandler(element, "change", function (e) {
            var observable = valueAccessor();
            observable(e.target.files[0]);
            element.value = null;
            e.preventDefault();
        });
    }
};

// usage -----------------------------------------------------------------------------
function FileUploader() {
  var self = this;
  self.audioFile = ko.observable();
}

$(function() {
  var viewModel = new FileUploader();
  ko.applyBindings(viewModel);
});
<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>

<div class="btn-link prompt-audio-control">
  <div class="btn-file">
    <input type="file" data-bind="file: audioFile" accept="audio/wav|audio/pcm|audio|vox">
    <span> Upload </span>
  </div>
</div>

<hr>
View Model:
<pre data-bind="text: ko.toJSON($root, null, 2)"></pre>

If setting the <input type="file">'s value to null is a good idea is another matter, since the event's files collection contains information about the files the user selected, it does not contain the files themselves. Setting the form control to null effectively destroys the user selection. If you want to actually upload the file at some point, more plumbing will be necessary.

Upvotes: 1

Related Questions