I have two sibling inputs inside a KO foreach
:
<input type="text" name="background_color" data-bind="value: $data.background_color">
<input type="hidden" name="background_color" data-bind="value: $data.background_color">
and the generated DOM looks like:
<input type="text" name="background_color" data-bind="value: $data.background_color">
<input type="hidden" name="background_color" data-bind="value: $data.background_color" value="#c9311b">
I'm getting that via jQuery's html
function and then using that HTML elsewhere, but when I use it elsewhere, the text input's value isn't set.
My fix is to use the attr
binding instead, which works
data-bind="attr: {'value':$data.background_color}"
but that's painful. Why is the value is only set for input[type="hidden"]
?
Using KO v3.3.0.
The current value of a text input isn't reflected in the DOM at the element/attribute level at all, and so innerHTML
or jQuery's html()
won't see it.
Solution below, but first let's just look at that behavior of value
and why it's different for type="text"
vs. type="hidden"
.
value
property vs. value
attributeThe value
attribute is not the value of a text input, it's the default value of a text input. Setting a text input's value does not set the attribute. In contrast, it doesn't make sense for a hidden input to have the differentiation between a "default value" and a "current value," and so setting the value
property does set the attribute, and so you see that attribute in the generated HTML.
This behavior of value
is not related to KO, it happens just with raw DOM manipulation:
document.getElementById("the-text-field").value = "text field value";
document.getElementById("the-hidden-field").value = "hidden field value";
<input id="the-text-field" type="text" name="background_color" >
<input id="the-hidden-field" type="hidden" name="background_color">
This is covered by the HTML5 specification here where it talks about the value
property and the different modes it has:
The
value
IDL attribute allows scripts to manipulate the value of an input element. The attribute is in one of the following modes, which define its behavior:value
On getting, it must return the current value of the element. On setting, it must set the element's value to the new value, set the element's dirty value flag to true, invoke the value sanitization algorithm, if the element's type attribute's current state defines one, and then, if the element has a text entry cursor position, should move the text entry cursor position to the end of the text field, unselecting any selected text and resetting the selection direction to none.
default
On getting, if the element has a value attribute, it must return that attribute's value; otherwise, it must return the empty string. On setting, it must set the element's value attribute to the new value.
...(there are a couple of other modes)
...and here, where it says that input type="hidden"
's value
uses the "default" mode but input type="text"
uses the "value" mode (you have to scroll down a bit to the "IDL attributes and methods" table; value
is the third one listed).
The solution, as you found, is to ensure that the value
attribute is set. You can do that with the attr
binding, as you mentioned, but it's a bit painful as you said. Instead, you can give yourself your own custom binding that sets both the value and the default value; since the default value is reflected in the DOM as the value
attribute, you'll see it in html()
:
// The binding handler
ko.bindingHandlers.valueWithAttr = {
update: function(element, valueAccessor) {
element.value = element.defaultValue = ko.unwrap(valueAccessor());
// Or we could do:
// var value = ko.unwrap(valueAccessor());
// element.value = value;
// element.setAttribute("value", value);
// ...but since that's what setting `defaultValue` does...
}
};
// Using it
var vm = {
background_color: "blue"
};
ko.applyBindings(vm, document.body);
$("<pre>").text(
"html of the inputs: " +
$("#container").html()
).appendTo(document.body);
<div id="container">
<input type="text" name="background_color" data-bind="valueWithAttr: $data.background_color">
<input type="hidden" name="background_color" data-bind="valueWithAttr: $data.background_color">
</div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.3.0/knockout-min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
very nice explanation, it pointed me to my problem that actually was that if i copying the contents with jquery´s .html() and pasting somewhere else, the value of only gets visually displayed if the value attribute is set, thx i will mark this as answer because it is a very nice answer, unfortunatly i have to go on with my bloody fix
@johnSmith: My pleasure, and I learned something (I didn't know setting the
value
property on hidden inputs set their attribute until I saw your question and tried it, which made me go digging into the spec).I came back to check on this question out of curiosity. Although I initially though it's probably something wrong with the way the OP uses Knockout, it turned out to be something interesting indeed and I have also learned something new.