Knockout js - Getting string length of observable - javascript

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

Related

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')});
}

Knockout multiple click bindings do not work with IE8

The problem:
Multiple click binding do not work in IE8.
The code:
var Cart = function() {
var self = this;
self.books = ko.observableArray();
self.cds = ko.observableArray();
};
var TheModel = function() {
var self = this;
self.cart = ko.observable(new Cart());
self.showAddBook = function() {
self.cart.books.push(/* new book */);
};
self.showAddCD = function() {
self.cart.cds.push(/* new cd */);
};
};
<div data-bind="with: cart">
<h1>Books<h1>
<button data-bind="click: $parent.showAddBook">Add</button>
<div data-bind="foreach: books">
<span data-bind="text: name"></span> <!-- book has a name property -->
</div>
<hr/>
<h3>CDs</h3>
<button data-bind="click: $parent.showAddCD">Add</button>
<div data-bind="foreach: cds">
<span data-bind="text: name"></span> <!-- cd has a name property -->
</div>
</div>
Background:
Apologies in advance. I don't have access to jsFiddle at work.
I have a deadline to get this piece of work complete which is why I am using knockout with jQuery. Would love to use Angular but can't because we have to support IE8. Would love to use Durandal but I have no experience of it and don't have the time just yet to learn it and finish this piece of work.
A user can create a new book or a new cd and add it to a collection. Not real-world example but reflects the problem I am solving.
A user can click on an Add button, this launches a jQuery dialog which captures some information about a book. This is then saved to the observable array on the model, and the list of books gets updated.
Question:
Why does IE8 only seem to bind the first click and not the second? If I click to add a book the dialog is shown. If I click to add a cd, nothing. I have debugged and the function does not get called.
TIA
As far as I can tell, neither of them should work, and not on any browser (rather than just not working on IE8), because both functions have the same problem: They don't unwrap cart:
self.cart.books.push(/* new book */);
// ^^^^^^
cart is an observable, so you need:
self.cart().books.push(/* new book */);
// ^^
...and similarly for the CDs stuff.
If you fix that, it works (even on IE8):
var Cart = function() {
var self = this;
self.books = ko.observableArray();
self.cds = ko.observableArray();
};
var TheModel = function() {
var self = this;
self.cart = ko.observable(new Cart());
self.showAddBook = function() {
self.cart().books.push({name: "New book " + (+new Date())});
};
self.showAddCD = function() {
self.cart().cds.push({name: "New CD " + (+new Date())});
};
};
ko.applyBindings(new TheModel(), document.body);
<div data-bind="with: cart">
<h1>Books<h1>
<button data-bind="click: $parent.showAddBook">Add</button>
<div data-bind="foreach: books">
<span data-bind="text: name"></span> <!-- book has a name property -->
</div>
<hr/>
<h3>CDs</h3>
<button data-bind="click: $parent.showAddCD">Add</button>
<div data-bind="foreach: cds">
<span data-bind="text: name"></span> <!-- cd has a name property -->
</div>
</div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
Apologies for slightly incorrect example but I had missed one level of nesting. My example is not a true reflection of my complex model and implementation but I worked out the cause of the problem.
My Cart in this example has a selectedItem property (an observable) of type object that has the array of books (observable array) and CDs (observable array).
var Items = function () {
this.books = ko.observableArray();
this.cds = ko.observableArray();
}
var Cart = function() {
this.selectedItem = ko.observable(new Items());
}
var Model = function () {
this.cart = new Cart();
}
I was using the knockout 'with' binding and setting the context to cart.selectedItem
<div data-bind="with: cart.selectedItem"> ... </div>
With this approach I noticed that only the first click (add book) was working. Clicking on Add CD was doing nothing.
I changed the context from cart.selectedItem to cart and set the foreach binding (that displays the list of books and cds) to selectedItem().books and selectedItem().cds and that worked in IE8 and other browser.
If I change the context using the knockout 'with' binding back to cart.selectedItem then only the first click works.
Hope that helps anyone else who encounters this problem.

Show default value when Knockout observable undefined or JS disabled

Using Knockout.js, is there a way to have an element's original content show if the observable bound to it is undefined?
<p data-bind="text: message">Show this text if message is undefined.</p>
<script>
function ViewModel() {
var self = this;
self.message = ko.observable();
};
ko.applyBindings(new ViewModel());
</script>
I know there are workarounds using visible, hidden or if but I find those too messy; I don't want the same element written out twice, once for each condition.
Also, I don't want to use any sort of default observable values. Going that route, if JS is disabled then nothing shows up. Same for crawlers: they would see nothing but an empty <p> tag.
To summarize, I want to say "Show this message if it exists, otherwise leave the element and its text alone."
The reasoning behind this is that I want to first populate my element using Razor.
<p data-bind="text: message">#Model.Message</p>
And then, in the browser, if JS is enabled, I can do with it as I please. If, however, there is no JS or the user is a crawler, they see, at minimum, the default value supplied server side via Razor.
You can simply use the || operator to show a default message in case message is undefined. Plus put the default text as content:
<p data-bind="text: message() || '#Model.Message' ">#Model.Message</p>
If javascript is disabled, the binding will be ignored and you will have the content displayed instead.
JSFiddle
Try this out
<p data-bind="text: message"></p>
<script>
function ViewModel() {
var self = this;
self.text = ko.observable();
self.message = ko.computed(function(){
if(self.text() != undefined){
return self.text();
}else{
return "Show this text if message is undefined.";
}
});
};
ko.applyBindings(new ViewModel());
</script>
You can set a default value like that in a number of ways, simplest way is to set the default observable value, as your JS file will be incorporated in to your HTML and will be able to access #Model.Message, so you can set a default value:
self.message = ko.observable(#Model.Message);
Here are a few other variations:
var viewModel = {
message: ko.observable(),
message1: ko.observable('Show this text if message is undefined.')
};
ko.applyBindings(viewModel);
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>
<h2>Option 1</h2>
<i>Set default value of observable: self.message1 = ko.observable(#Model.Message);</i>
<p data-bind="text: message1"></p>
<h2>Option 2</h2>
<i>Check for undefined and replace</i>
<p data-bind="text: message() ? message : message1"></p>
<h2>Option 3</h2>
<i>Use KO if syntax to display content if defined/undefined</i>
<!-- ko if: message() -->
<p data-bind="text: message"></p>
<!-- /ko -->
<!-- ko ifnot: message() -->
<p data-bind="text: message1"></p>
<!-- /ko -->

How can I copy this knockout observable?

I am trying to add a "default" object to my Knockout observableArray. The way my code stands currently, I end up adding the exact same object to the array instead of a new instance of the object. I tried using jQuery's .extend(), but, that didn't work out so I am looking for input.
Here is a demonstration to show my problem: http://jsfiddle.net/2c8Fx/
HTML:
<div data-bind="foreach: People">
<input type="text" data-bind="value: Name" />
</div>
<button type="button" data-bind="click: AddPerson">Add Person</button>
<pre data-bind="text: ko.toJSON($data, null, 2)"></pre>
Script:
function ViewModel() {
var self = this;
self.EmptyPerson = ko.mapping.fromJS({Name: null, Age: null});
self.People = ko.observableArray([self.EmptyPerson]);
self.AddPerson = function() {
self.People.push(self.EmptyPerson);
};
}
ko.applyBindings(new ViewModel());
This doesn't work because the observableArray is actually holding a reference to the same object for each index.
What's the best way to create a new instance of my Knockout object?
function ViewModel() {
var self = this;
self.People = ko.observableArray();
self.AddPerson = function() {
self.People.push(ko.mapping.fromJS({Name: null, Age: null}));
};
}
ko.applyBindings(new ViewModel());
I did this and it works like you think it would. I hope this helps.
Check out a working example here

Knockoutjs. How to compute data changed inside observable array

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>​

Categories

Resources