Knockout JS - data-bind multiple values - javascript

Question about JS Knockout library - I have three inputs, all data-Bind-ed to the same variable. Two have a boolean value of false and one has a boolean value of true. (I can't change them to ints, unfortunately, which would make this problem easier). Although the two false-valued inputs share behavior, I need to differentiate between them somehow to trigger slightly different behaviors.
Is it possible to data-bind each to another variable, with different values? So instead of each being
<input data-Bind="checked:test" value="false">
I would have something like
<input data-Bind="test, test2" value="false, 1">
and
<input data-Bind="test, test2" value="false, 2">?
I tried that directly and didn't work so I don't know if it's possible. Thanks so much.

You cant bind multiple variables directly but creating a custom bind function do the trick for you.
Example : http://jsfiddle.net/gurkavcu/ePW8Y/
** Change input value (true , false) to trigger the update function
HTML
<input data-bind="customData: test , v1 : test2"/>
<div>
<span data-bind ="text : test"/>
</div>
<div>
<span data-bind ="text : test2"/>
</div>
JS
ko.bindingHandlers.customData = {
init: function(element, valueAccessor, allBindingsAccessor, viewModel) {
$(element).change(function () {
valueAccessor()(element.value);
});
},
update: function(element, valueAccessor, allBindingsAccessor, viewModel) {
var value =ko.utils.unwrapObservable(valueAccessor());
var v1 = allBindingsAccessor().v1;
if(value === "true") {
v1("1");
console.log(v1());
}
else if(value === "false") {
v1("2");
console.log(v1());
}
}
};
function ViewModel() {
this.test = ko.observable(false);
this.test2 = ko.observable("2");
};
$(function() {
var viewModel = new ViewModel();
ko.applyBindings(viewModel);
})​
Modify the update function for your needs. You can add any number of variable to the binding with v1 : ... , v2 : ... , v3 : ... and access it via allBindingsAccessor().v1 , allBindingsAccessor().v2 , allBindingsAccessor().v3

Related

Knockout custom handler to hide textbox value if it is disabled

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));
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.
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.
Release Notes
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.
Compatibility Issue

Knockout JS clear inputs on focus

I'm building an app with knockout that's very number/input heavy. I'm refractoring my code to put everything into observableArrays so I can then bind these arrays to my HTML and all that is visible in the HTML is a template and Knockout is doing the leg-work to render all the elements inside this template.
My problem now lies with the fact my pre-existing clear input fields on focus script isn't working and I'm not entirely sure why or how to get it working.
This is the pre-existing script and is written with jQuery and I feel that there's now a conflict happening between Knockout and jQuery. Unfortunately I don't know to go about rewriting the script in Knockout.
$('input:not(.total-val)').on('focus', function() {
var default_value = $(this).val();
if ($(this).val() == default_value) {
$(this).val('');
}
$(this).blur(function () {
if($(this).val().length == 0) /*Small update*/
{
$(this).val(default_value);
}
});
});
Thanks in advance!
You should use Knockout's hasFocus binding instead of jQuery's on('focus'). I fear that jQuery is conflicting with Knockout's binding events and causing your text to disappear on focus. Here's an example:
<p>
Name:
<b data-bind="visible: !editing(), text: name, click: edit"> </b>
<input data-bind="visible: editing, value: name, hasFocus: editing" />
</p>
<p><em>Click the name to edit it; click elsewhere to apply changes.</em></p>
JavaScript:
function PersonViewModel(name) {
// Data
this.name = ko.observable(name);
this.editing = ko.observable(false);
// Behaviors
this.edit = function() { this.editing(true) }
}
ko.applyBindings(new PersonViewModel("Bert Bertington"));
There was a conflict was with jQuery vs Knockout fighting for control over the generated inputs. I fixed this issue be refactoring the clear input script in Vanilla.
$('input:not(.total-val)').attr({'onfocus':'onFocus(this)', 'onblur':'onBlur(this)'})
var default_value;
onFocus = function(input) {
default_value = input.value;
if (input.value == default_value) {
input.value = ''
}
}
onBlur = function(input) {
if (input.value == '') {
input.value = default_value;
}
}

Using checkedValue binding with radio buttons

I'm trying to make use of the checkedValue binding introduced in knockout version 3, with radio buttons , but am not getting the behavior I expect.
Here's an example: (the viewModel has two properties; list is an array; checkedVal is an observable)
<div data-bind="foreach:list">
<input type="radio" data-bind="
checkedValue: {
data: $data,
index: $index(),
},
checked: $parent.checkedVal
"/>
<span data-bind="text: $data"></span>
</div>
JSFiddle
I expect the radio buttons to behave normally, and checkedVal to be an object containing the data and index. checkedVal is as I expect, but the radio buttons don't select. Oddly, in my actual code the behavior is inconsistent; sometimes the radio butttons work and sometimes they don't, but it consistently doesn't work in the fiddle, as far as I can see.
Is this a bug, or am I misunderstanding how this should be working?
Your checkedValue binding becomes a function as follows:
function () {
return {
data: $data,
index: $index(),
};
}
Each time the checked binding updates, it calls this function to get the value. But the function always returns a new object. Even though the objects contains the same data, Knockout doesn't see them as the same.
You can solve this by making the value a string.
<input type="radio" data-bind="
checkedValue: JSON.stringify({
data: $data,
index: $index(),
}),
checked: $parent.checkedVal
"/>
Or by binding to a consistent value.
<input type="radio" data-bind="
checkedValue: $data,
checked: $parent.checkedVal
"/>
EDIT:
You can use a custom binding that follows the same pattern as checked, but allows for a comparison function.
ko.bindingHandlers.radioChecked = {
init: function (element, valueAccessor, allBindings) {
ko.utils.registerEventHandler(element, "click", function() {
if (element.checked) {
var observable = valueAccessor(),
checkedValue = allBindings.get('checkedValue');
observable(checkedValue);
}
});
},
update: function (element, valueAccessor, allBindings) {
var modelValue = valueAccessor()(),
checkedValue = allBindings.get('checkedValue'),
comparer = allBindings.get('checkedComparer');
element.checked = comparer(modelValue, checkedValue);
}
};
Then objects can be compared by contents.
this.itemCompare = function(a, b) {
return JSON.stringify(a) == JSON.stringify(b);
}
jsFiddle: http://jsfiddle.net/mbest/Q4LSQ/
It appears that this issue has been resolved in newer versions of KO. As of version 3.2 I'm no longer seeing this behavior mentioned in my original question.
Here's a working JSFiddle, identical to the original, except with KO updated to 3.2 .

Ember.TextField binding changed in Ember RC1?

I'm trying to build a view that will initially display text. If the user double-clicks, it will replace that text with an input field. This way the user can easily update the text (like using the "contenteditable" attribute).
I have an approach that works in Ember pre4, but not in Ember RC1. In RC1, the Ember.TextField does not initialize to the parent view's value property. When you double-click the label text, it creates an empty input field. Here are two fiddles:
Pre4 (working): http://jsfiddle.net/mattsonic/cq5yy/5
RC1 (same code - not working): http://jsfiddle.net/mattsonic/UUac9/15
Any idea what changed inside Ember? Thanks.
Here is the code:
App.InputView = Ember.TextField.extend({
classNames: ["input-small"],
valueBinding: "parentView.value",
didInsertElement: function () {
this.$().focus()
},
focusOut: function () {
parent = this.get("parentView");
parent.setLabelView();
}
});
App.LabelView = Ember.View.extend({
tagName: "span",
template: Ember.Handlebars.compile("{{view.value}}"),
valueBinding: "parentView.value",
doubleClick: function () {
parent = this.get("parentView");
parent.setInputView();
}
});
App.LabelEditView = Ember.ContainerView.extend({
tagName: "span",
labelView: App.LabelView.extend(),
inputView: App.InputView.extend(),
didInsertElement: function () {
this.setLabelView();
},
setInputView: function () {
this.set("currentView", this.get("inputView").create());
},
setLabelView: function () {
this.set("currentView", this.get("labelView").create());
}
});
I found a solution that I don't like at all. But, it solves the problem as described.
focusIn: function() {
var val = this.get("parentView.value");
this.set("value", "");
this.set("value", val);
},
If you set the input field's value to the correct value during the focusIn event, it still fails. But, if you set the input field's value to a different value and then switch it back, the input field will appear with the correct value.
I would love to know a better way to solve this problem. The Ember pre4 solution is more much elegant than this.
Working fiddle: http://jsfiddle.net/mattsonic/UUac9/19/

How to update knockoutjs view model on user confirm?

I have a edit in place section to which I want to add a confirmation of changes before the knockoutjs model is updated.
Here's the jsFiddle example of what I have now.
Here's what I would like it to do.
User clicks on editable section
textbox appears with save/cancel buttons next to it.
if user makes a change and clicks save, view model is updated
if user makes a change, but decides to keep the original content, they click cancel, view model remains unchanged, texbox is hidden, and editable element remains unchanged.
The behavior of the cancel click is what I'm not sure how to implement. Can anyone suggest how this could be done?
I prefer to use custom binding handler for this.
Example http://jsfiddle.net/7v6Dx/10/
Html
<div>
<span class="editField">
<span data-bind="text: Address1">Click here to edit</span>
<input type="text" data-bind="clickEditor: Address1">
</span>
</div>​
JavaScript
ko.bindingHandlers.clickEditor = {
init: function (element, valueAccessor, allBindingsAccessor, viewModel) {
var $element = $(element).hide();
var $text = $element.prev();
var $buttons = $("<span class='editConfirm'> \
<button class='saveEdit' type='button'>Save</button> \
<button class='cancelEdit' type='button'>Cancel</button> \
</span>").hide().insertAfter($element);
var $editElements = $buttons.add($element);
ko.utils.domNodeDisposal.addDisposeCallback(element, function () {
$buttons.remove();
});
var _toggle = function(edit) {
$text[edit? 'hide' : 'show']();
$editElements[edit? 'show' : 'hide']();
}
$text.click(function(e) {
_toggle(true);
});
$editElements.find('.saveEdit').click(function() {
_toggle(false);
valueAccessor()($element.val());
});
$editElements.find('.cancelEdit').click(function() {
_toggle(false);
$(element).val(ko.utils.unwrapObservable(valueAccessor()));
});
}
, update: function (element, valueAccessor) {
$(element).val(ko.utils.unwrapObservable(valueAccessor()));
}
};
$(document).ready(function() {
var helpText = "Click here to edit";
function appViewModel() {
this.Address1 = ko.observable(helpText);
}
ko.applyBindings(appViewModel());
});​
I was thinking you could probably use a writable computed property to handle this. But it might be easier to just have to separate properties. One property is the real property and the other shadows it. When you bring up the editable section, it's actually bound to the shadow value. When the ok button is clicked, you copy the shadow value to the real value. If cancel is clicked, you do the opposite (copy the real value to the shadow value).

Categories

Resources