Reputation: 3289
I have a form that basically has a handful of properties that are shared between a few items. When you select the radio button for the item the text boxes enable for data entry, only one item can be selected at a time.
I have everything setup and working except I do not want the bound values to display in the textbox if the control is disabled. I have been trying to work with the handlers but I am having a hell of a time trying to understand how to make things work the way I need. I have looked at many articles by Ryan and the custom handlers he has provided but I need an epiphany, but until then I am seeking your help. Also, is there a more appropriate way to handle the IsEnabled function I have created or is that the best way?
Here is the JSFiddle
Updated JSFiddle, instead of doing the value I am attempting to create a custom handler that disabled and deletes the value. It kinda works but it stops after a few updates and the value doesn't get updated.
Here is some sample HTML:
<ul>
<li>
<input type="radio" name="item" value="1" data-bind="checked:Selected" /> Item 1 <input type="text" data-bind="value:Price, enable:IsEnabled('1')" />
</li>
<li>
<input type="radio" name="item" value="2" data-bind="checked:Selected" /> Item 2 <input type="text" data-bind="value:Price, enable:IsEnabled('2')" />
</li>
<li>
<input type="radio" name="item" value="3" data-bind="checked:Selected" /> Item 3 <input type="text" data-bind="enabledValue:Price, enable:IsEnabled('3')" />
</li>
<li>
<input type="radio" name="item" value="4" data-bind="checked:Selected" /> Item 4 <input type="text" data-bind="enabledValue:Price, enable:IsEnabled('4')" />
</li>
</ul>
Here is the sample JS:
var vm = {
Selected: ko.observable('1'),
Price: ko.observable(12),
IsEnabled: function(item){
var selected = this.Selected();
return (selected == item)
}
}
ko.applyBindings(vm);
(function (ko, handlers, unwrap, extend) {
"use strict";
extend(handlers, {
enabledValue: {
init: function (element, valueAccessor, allBindings) {
var bindings = allBindings();
var enabled = ko.unwrap(bindings.enable);
var value = unwrap(valueAccessor());
if (enabled)
handlers.value.init();
},
update: function (element, valueAccessor, allBindings) {
var bindings = allBindings();
var enabled = ko.unwrap(bindings.enable);
var value = unwrap(valueAccessor());
handlers.value.update(element,function() {
if(enabled)
return valueAccessor(value);
});
}
}
});
}(ko, ko.bindingHandlers, ko.utils.unwrapObservable, ko.utils.extend));
Upvotes: 1
Views: 416
Reputation: 3289
Since my additions to the answer have been edited out I have added this answer to help those new to KO.
Here is a KO 3.0 implementation using ko.applyBindingAccessorsToNode.
extend(handlers, {
enableValue: {
init: function (element, valueAccessor, allBindings) {
var showValue = ko.computed({
read: function () {
if (unwrap(allBindings().enable)) {
return valueAccessor(); // CHANGED
} else {
return '';
}
},
write: valueAccessor //CHANGED
});
ko.applyBindingAccessorsToNode(element, { value: showValue }); //CHANGED
}
}
});
As stated in the release notes there is no official documentation for it yet but this is what I was able to put together. I used the group message to determine the differences. Hopefully this will save someone time until it has more documentation.
ko.applyBindingsToNode is superseded by ko.applyBindingAccessorsToNode. The second parameter takes an object with pairs of bindings and value-accessors (functions that return the binding value). It can also take a function that returns such an object. (This interface isn't currently documented on the website.)
Group Message from Michael Best stating it is better.
Upvotes: 0
Reputation: 3907
Tony. I've just simplified your sample and got it working with sharing same value property between different items. The main idea that a binding will store internal computed and will bind an element against it.
extend(handlers, {
enableValue: {
init: function (element, valueAccessor, allBindings) {
var showValue = ko.computed({
read: function(){
if (unwrap(allBindings().enable)) {
return unwrap(valueAccessor());
} else {
return '';
}
},
write: valueAccessor()
});
ko.applyBindingsToNode(element, { value: showValue });
}
}
});
http://jsfiddle.net/7w566pt9/4/
Note that in KO 3.0 ko.applyBindingsToNode
is renamed to ko.applyBindingAccessorsToNode
.
But wouldn't it have more sense to make the bindings remember last entered value for each item? It's quite simple to implement.
Update
Remembering last edited value for the particular item is similar in the manner that you should keep that value internally like showValue
. Let's name it lastValue
:
extend(handlers, {
enableValue: {
init: function (element, valueAccessor, allBindings) {
// Create observable `lastValue` with some default content.
// It will be created for EVERY binding separately.
var lastValue = ko.observable(0);
// If an item is currently enabled then set `lastValue` to the actual value.
if (unwrap(allBindings().enable)) lastValue(unwrap(valueAccessor()));
// This piece will be executed only once (for the selected item) and other
// items will store default value in `lastValue`!
// It's the internal anonymous computed intended to update bound
// price to reflect currently edited value.
ko.computed(function(){
if (unwrap(allBindings().enable)) valueAccessor()(lastValue());
});
// Note that passed function will be triggered whenever item is enabled
// and/or `lastValue` changes.
// Here we just change valueAccessor() to `lastValue`.
var showValue = ko.computed({
read: function(){
if (unwrap(allBindings().enable)) {
return lastValue();
} else {
return '';
}
},
write: lastValue
});
ko.applyBindingsToNode(element, { value: showValue });
}
}
});
http://jsfiddle.net/7w566pt9/8/
I hope it is nearly what you expected. Usually in such cases the real problem is not implementing a feature but describing how the feature should work.
Upvotes: 2