Knockoutjs. How to compute data changed inside observable array - javascript

Please, look at my text. I try to use observableArray of knockoutjs and foreach to compute data of array.
Example 1 works fine: total sum computed if you change data in the fields. But Example 2 is not working.
<html>
<head>
<title></title>
<script type='text/javascript' src='/js/jquery-1.8.2.min.js'></script>
<script type='text/javascript' src='/js/knockout-2.1.0.debug.js'></script>
</head>
<body>
<p>Example 1</p>
<div>
<p>
<input data-bind="value: fnum1" />
<input data-bind="value: fnum2" />
<span data-bind="text: ftotsum"></span>
</p>
</div>
<p>Example 2</p>
<div>
<p>
<!-- ko foreach: fields -->
<input data-bind="value: $data" />
<!-- /ko -->
<span data-bind="text: ltotsum"></span>
</p>
</div>
</body>
<script>
function vm(){
//Calc Example 1
var self = this;
self.fnum1 = ko.observable(1);
self.fnum2 = ko.observable(2);
self.ftotsum = ko.computed(function(){
return parseFloat(self.fnum1()) + parseFloat(self.fnum2());
});
//Calc Example 2
self.fields = ko.observableArray([1, 2]);
self.ltotsum = ko.computed(function(){
var total = 0;
ko.utils.arrayForEach(self.fields(), function(item) {
total += parseFloat(item);
})
return total;
});
};
ko.applyBindings(new vm());
</script>
</html>

EDIT: Got fiddle working, Raffaele is correct in saying you need to wrap the observable inside an object, but you can do it within the array creation itself and I like to use the ko.utils to unwrap my observables, it does the same thing for observables but it won't crash if there is a non-observable passed to it. See fiddle for full example.
An observableArray doesn't make the values passed observable, this is a common mistake. An observableArray just observes the modifications to the array and not the values. If you want to have your values inside your array be observable you have to make them so.
function vm(){
//Calc Example 1
var self = this;
self.fnum1 = ko.observable(1);
self.fnum2 = ko.observable(2);
self.ftotsum = ko.computed(function(){
return parseFloat(self.fnum1()) + parseFloat(self.fnum2());
});
//Calc Example 2
self.fields = ko.observableArray([{"num":ko.observable(1)},{"num":ko.observable(2)}]);
self.ltotsum = ko.computed(function(){
var total = 0;
ko.utils.arrayForEach(self.fields(), function(item) {
total += parseFloat(ko.utils.unwrapObservable(item.num));
});
return total;
});
};
ko.applyBindings(new vm());
Should work with the example above now.

The documentation says:
Key point: An observableArray tracks which objects are in the array, not the state of those objects
Simply putting an object into an observableArray doesn’t make all of
that object’s properties themselves observable. Of course, you can
make those properties observable if you wish, but that’s an
independent choice. An observableArray just tracks which objects it
holds, and notifies listeners when objects are added or
removed.
Your second example doesn't work because the value of the input fields is not bound to the values in the array. That values in the array are used only once, in the foreach binding, but when you type in the input boxes, nothing triggers KO.
Here is a working fiddle with a solution implemented. I used a helper ObsNumber
function vm(){
var self = this;
var ObsNumber = function(i) {
this.value = ko.observable(i);
}
self.fields = ko.observableArray([new ObsNumber(1) ,
new ObsNumber(2)]);
self.sum = ko.computed(function(){
var total = 0;
ko.utils.arrayForEach(self.fields(), function(item) {
total += parseFloat(item.value());
});
return total;
});
};
ko.applyBindings(new vm());
and the following markup
<div>
<p>
<!-- ko foreach: fields -->
<input data-bind="value: $data.value" />
<!-- /ko -->
<span data-bind="text: sum"></span>
</p>
</div>​

Related

Does a writable computed observable really need an extra internal observable?

I am trying to use a writable computed observable, but do not really get it why I cannot get some data in the observable by just typing in it. I found that I need an extra observable to copy content from the write: to the read:, which seems weird.
self.fullName = ko.pureComputed({
read: function () {
return ...data from other observables to show in the observable;
},
write: function (value) {
// value is the content in the input
// can be sent to other observables
},
owner: self
});
I found that in the above model, what you type in the observable is not really inside.
In the complete example below (including test output and comments) I use an extra observable to copy data from write: to read:. Crucial in my project is the checkbox to decide if you get two observables filled identically by typing in one of them, or differently by typing in both of them.
<!DOCTYPE html>
<html lang="en">
<head>
<title>Writable computed observables</title>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<script src='https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.1/knockout-min.js'></script>
</head>
<body>
<h1>Writable computed observables</h1>
<p>See: Knockout Doc</p>
<h2>Original example</h2>
<div>
First name: <input data-bind="textInput: firstName" />
<span data-bind="text: firstName"></span>
</div>
<div>
Last name: <input data-bind="textInput: lastName" />
<span data-bind="text: lastName"></span>
</div>
<div class="heading">
Hello, <input data-bind="textInput: fullName" />
<span data-bind="text: fullName"></span>
</div>
<h2>My example</h2>
<div>
Name: <input data-bind="textInput: Name" />
<span data-bind="text: Name"></span>
</div>
<div>
Mirror first name? <input type="checkbox" data-bind="checked: cbMirror" />
<span data-bind="text: cbMirror"></span>
</div>
<script>
function MyViewModel() {
var self = this;
// example from knockout site:
self.firstName = ko.observable('Planet');
self.lastName = ko.observable('Earth');
self.fullName = ko.pureComputed({
read: function () {
//return;
return self.firstName() + " " + self.lastName();
},
write: function (value) {
// value is the content in the input field, visible on form,
// but apparently not yet in the observable.
// now copy this value to first/last-name observables,
// that in turn copy it to the read-function,
// that returns it to the observable.
var lastSpacePos = value.lastIndexOf(" ");
if (lastSpacePos > 0) { // Ignore values with no space character
self.firstName(value.substring(0, lastSpacePos)); // Update "firstName"
self.lastName(value.substring(lastSpacePos + 1)); // Update "lastName"
}
},
owner: self
});
// checkbox whether or not to mirror between two fields
self.cbMirror = ko.observable(false);
// this observable is to help the writable computed observable to copy input from write() to read()
self.tmpName = ko.observable();
// the writable computed observable that may mirror another field, depending on the checkbox
self.Name = ko.pureComputed({
read: function () {
return self.cbMirror() ? self.firstName() : self.tmpName();
},
write: function (value) {
//if (self.cbMirror()){
// self.firstName(value);
//}else{
self.tmpName(value);
//}
},
owner: self
});
}
ko.applyBindings(new MyViewModel());
</script>
</body>
</html>
The question, hence: is there really no better way, to directly get some content from write: to read: without the extra observable self.tmpName?
Update:
With the understanding gained from the Answer below, I could simplify the write: part of my example code, see the unneeded code that I commented-out.
Yes you need an observable if you want to store the user input. A computed doesn't store information it only modifies it. It's like the difference between a variable and a function. Functions don't store their values for later viewing it's only modifying an input and giving an output.
the writable computed observable doesn't store data. It passes the data to the backing observable. That's the entire point of the write portion is to take the value and store it somewhere. If you add another span to look at the value of tmpName you'll see that it's storing whatever you type unless cbMirror is checked.

cant make an object I added to a knockout model observable

Here is the fiddle demonstrating the problem http://jsfiddle.net/LkqTU/31955/
I made a representation of my actual problem in the fiddle. I am loading an object via web api 2 and ajax and inserting it into my knockout model. however when I do this it appears the attributes are no longer observable. I'm not sure how to make them observable. in the example you will see that the text box and span load with the original value however updating the textbox does not update the value.
here is the javascript.
function model() {
var self = this;
this.emp = ko.observable('');
this.loademp = function() {
self.emp({
name: 'Bryan'
});
}
}
var mymodel = new model();
$(document).ready(function() {
ko.applyBindings(mymodel);
});
here is the html
<button data-bind="click: loademp">
load emp
</button>
<div data-bind="with: emp">
<input data-bind="value: name" />
<span data-bind="text: name"></span>
</div>
You need to make name property observable:
this.loademp = function(){
self.emp({name: ko.observable('Bryan')});
}

Bind a checkbox to a plain string in Knockout.js

In my viewmodel i have tons of checkboxes bound to plain strings:
<input type="checkbox" value="CODE" data-bind="checked: itemValue" />
Until now, i'm using an observable array to resolve the true/false value of the checkbox to the value that i need:
var viewModel = {
itemValue: ko.observableArray()
};
Which is the simplest and shortest way, if there is one, to bind a checkbox to a string value without the need to reference it as itemValue[0] ?
What i need is the string value if checked, null if unchecked.
Due to the large amount of observables in my viewmodel, i would avoid to use tons of conditions like if(itemValue) ...
Fiddle using an observableArray: https://jsfiddle.net/wu470qup/
If you want effortless markup, you'll have to keep track of the checkboxes you want to render in your viewmodel. Here's the easiest example:
allValues is a regular array of strings. Each of these strings will get a checkbox. itemValues is an observable array of the strings that have a checked checkbox. correctedValues is a computed array with null for each unchecked box, and a string for each checked one.
Notice that I use $data to refer to the current string value in the foreach.
var allValues = ["CODE", "ANOTHER_VAL"];
var itemValues = ko.observableArray();
var correctedValues = ko.computed(function() {
var all = allValues;
var checked = itemValues();
return all.map(function(val) {
return checked.indexOf(val) === -1 ? null : val;
});
});
var viewModel = {
allValues: allValues,
itemValues: itemValues,
correctedValues: correctedValues
};
ko.applyBindings(viewModel);
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>
<!-- ko foreach: allValues -->
<label>
<input type="checkbox" data-bind="checked: $parent.itemValues, checkedValue: $data" />
<span data-bind="text: $data"><span/>
</label>
<!-- /ko -->
<pre data-bind="text: ko.toJSON($data, null, 2)"></pre>
A more elegant approach would be to create viewmodels for each checkbox. For example:
var CheckboxItem = function() {
this.label = ko.observable("CODE");
this.checked = ko.observable(false);
this.id = ko.computed(function() {
return this.checked() ? this.label() : null;
}, this);
};
With the HTML template:
<label>
<input type="checkbox" data-bind="checked: checked" />
<input data-bind="value: label" />
</label>
Then, in the parent viewmodel, you can store an observable array of CheckBoxItems and have a computed array that holds all id properties.

Knockout js - Getting string length of observable

This seems to be a simple issue but I can't seem to figure it out
I just need to display the length of a string that is observable. I have tried achieving the result with a ko.computed() function as you can see in the code below, but it always returns zero.
Fiddle with an example
Html
<div id="vm">
<h2>The title is: <span data-bind="text: title"></span></h2>
<h2>The length is: <span data-bind="text: title.length"></span></h2>
<h2>Length from computed: <span data-bind="text: titleLength"></span></h2>
<input data-bind="value: title, valueUpdate: 'keyup'"/>
</div>
JavaScript
function VM() {
var self = this;
self.title = ko.observable();
self.titleLength = ko.computed(function() {
return self.title.length;
});
}
ko.applyBindings(VM(), document.getElementById('vm'));
Your computed version is almost correct. Change it by this
return self.title().length; // <-- Notice () after title
DEMO

How to subscribe to child observable from parent observable in Knockout

I have an KO observables like this DEMO
var list = function(){
var array = [{val :'1'}, {val :'2'}, {val :'3'}, {val :'4'}];
var that = this;
this.inputs = ko.observableArray();
array.forEach(function(obj){
var val = ko.observable(obj.val);
that.inputs.push(val);
//Subscribe to each element
val.subscribe(function(val){
console.log(val);
});
});
}
ko.applyBindings(new list());
And I am using inputs to populate data in fields like
<ul data-bind="foreach: inputs">
<input type="text" data-bind="value :$data" />
</ul>
So when ever I change the input data is there a way to know the parent is getting updated with new entered data since all values are observable.
So as per knockout-js-observable-array-changes-to-individual-observable-items
I make each item as observable and try to subscribe the changes
//Subscribe to each element
val.subscribe(function(val){
console.log(val);
});
but that too doesn't work , how to implement this knockout-js-observable-array-changes-to-individual-observable-items solution.
Don't put ko.observable directly into your ko.observableArray but create objects which have observable properties.
So change your array filling code to:
var val = ko.observable(obj.val);
that.obs.push({val: val});
And your view to:
<ul data-bind="foreach: inputs">
<input type="text" data-bind="value: val" />
</ul>
Demo JSFiddle.

Categories

Resources