Skip to content

Commit

Permalink
Make value binding work with input[type=file] fixes #849
Browse files Browse the repository at this point in the history
  • Loading branch information
mbest committed Dec 3, 2016
1 parent 11e3cb9 commit 766af4c
Show file tree
Hide file tree
Showing 2 changed files with 54 additions and 28 deletions.
9 changes: 9 additions & 0 deletions spec/defaultBindings/valueBehaviors.js
Original file line number Diff line number Diff line change
Expand Up @@ -304,6 +304,15 @@ describe('Binding: Value', function() {
}
});

it('Should bind to file inputs but not allow setting an non-empty value', function() {
var observable = ko.observable('zzz');
var vm = { prop: observable };

testNode.innerHTML = "<input type='file' data-bind='value: prop' />";
ko.applyBindings(vm, testNode);
expect(testNode.childNodes[0].value).toEqual("");
});

describe('For select boxes', function() {
it('Should update selectedIndex when the model changes (options specified before value)', function() {
var observable = new ko.observable('B');
Expand Down
73 changes: 45 additions & 28 deletions src/binding/defaultBindings/value.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
ko.bindingHandlers['value'] = {
'after': ['options', 'foreach'],
'init': function (element, valueAccessor, allBindings) {
var tagName = ko.utils.tagNameLower(element),
isInputElement = tagName == "input";

// If the value binding is placed on a radio/checkbox, then just pass through to checkedValue and quit
if (element.tagName.toLowerCase() == "input" && (element.type == "checkbox" || element.type == "radio")) {
if (isInputElement && (element.type == "checkbox" || element.type == "radio")) {
ko.applyBindingAccessorsToNode(element, { 'checkedValue': valueAccessor });
return;
}
Expand Down Expand Up @@ -30,7 +33,7 @@ ko.bindingHandlers['value'] = {

// Workaround for https://github.com/SteveSanderson/knockout/issues/122
// IE doesn't fire "change" events on textboxes if the user selects a value from its autocomplete list
var ieAutoCompleteHackNeeded = ko.utils.ieVersion && element.tagName.toLowerCase() == "input" && element.type == "text"
var ieAutoCompleteHackNeeded = ko.utils.ieVersion && isInputElement && element.type == "text"
&& element.autocomplete != "off" && (!element.form || element.form.autocomplete != "off");
if (ieAutoCompleteHackNeeded && ko.utils.arrayIndexOf(eventsToCatch, "propertychange") == -1) {
ko.utils.registerEventHandler(element, "propertychange", function () { propertyChangedFired = true });
Expand Down Expand Up @@ -64,40 +67,54 @@ ko.bindingHandlers['value'] = {
ko.utils.registerEventHandler(element, eventName, handler);
});

var updateFromModel = function () {
var newValue = ko.utils.unwrapObservable(valueAccessor());
var elementValue = ko.selectExtensions.readValue(element);
var updateFromModel;

if (elementValueBeforeEvent !== null && newValue === elementValueBeforeEvent) {
ko.utils.setTimeout(updateFromModel, 0);
return;
if (isInputElement && element.type == "file") {
// For file input elements, can only write the empty string
updateFromModel = function () {
var newValue = ko.utils.unwrapObservable(valueAccessor());
if (newValue === null || newValue === undefined || newValue === "") {
element.value = "";
} else {
valueUpdateHandler(); // reset the model to match the element
}
}
} else {
updateFromModel = function () {
var newValue = ko.utils.unwrapObservable(valueAccessor());
var elementValue = ko.selectExtensions.readValue(element);

var valueHasChanged = (newValue !== elementValue);
if (elementValueBeforeEvent !== null && newValue === elementValueBeforeEvent) {
ko.utils.setTimeout(updateFromModel, 0);
return;
}

if (valueHasChanged) {
if (ko.utils.tagNameLower(element) === "select") {
var allowUnset = allBindings.get('valueAllowUnset');
var applyValueAction = function () {
ko.selectExtensions.writeValue(element, newValue, allowUnset);
};
applyValueAction();
var valueHasChanged = (newValue !== elementValue);

if (!allowUnset && newValue !== ko.selectExtensions.readValue(element)) {
// If you try to set a model value that can't be represented in an already-populated dropdown, reject that change,
// because you're not allowed to have a model value that disagrees with a visible UI selection.
ko.dependencyDetection.ignore(ko.utils.triggerEvent, null, [element, "change"]);
if (valueHasChanged) {
if (tagName === "select") {
var allowUnset = allBindings.get('valueAllowUnset');
var applyValueAction = function () {
ko.selectExtensions.writeValue(element, newValue, allowUnset);
};
applyValueAction();

if (!allowUnset && newValue !== ko.selectExtensions.readValue(element)) {
// If you try to set a model value that can't be represented in an already-populated dropdown, reject that change,
// because you're not allowed to have a model value that disagrees with a visible UI selection.
ko.dependencyDetection.ignore(ko.utils.triggerEvent, null, [element, "change"]);
} else {
// Workaround for IE6 bug: It won't reliably apply values to SELECT nodes during the same execution thread
// right after you've changed the set of OPTION nodes on it. So for that node type, we'll schedule a second thread
// to apply the value as well.
ko.utils.setTimeout(applyValueAction, 0);
}
} else {
// Workaround for IE6 bug: It won't reliably apply values to SELECT nodes during the same execution thread
// right after you've changed the set of OPTION nodes on it. So for that node type, we'll schedule a second thread
// to apply the value as well.
ko.utils.setTimeout(applyValueAction, 0);
ko.selectExtensions.writeValue(element, newValue);
}
} else {
ko.selectExtensions.writeValue(element, newValue);
}
}
};
};
}

ko.computed(updateFromModel, null, { disposeWhenNodeIsRemoved: element });
},
Expand Down

0 comments on commit 766af4c

Please sign in to comment.