Knockout Js: foreachBinding - javascript

I have object models which is of format below,
models:
[
0: {
[functions]: ,
__proto__: { },
Main: "Sample",
sub: [
0: " Sub1",
1: " Sub2",
2: " sub3",
3: " sub4",
4: " sub5",
5: " sub6",
6: " sub7",
7: " sub8",
length: 8
]
},
1: { },
2: { },
I have listed first three elements of the object as sample. Also first element is elaborated. I am trying to list this as below.
Main 1
sub details
Main 2
sub details
I have tried like, I could not figure out why my data binding is not working. Following code not displaying anything in the screen.
<td>
<ul class="tree">
<li data-bind="foreach: models" >
<span data-bind="text: $data[$index].main"></span>
<ul data-bind="foreach: subDetails in models.sub">
<li><span data-bind="text: $data[$index].sub"></span></li>
</ul>
</li>
</ul>
</td>
Please check my fiddle here. Any help would be appreciated.

There are a lot of issues with your fiddle. Here is a list -
You have written ko.applybindigs which should have been ko.applyBindings
The model should be a part of your viewModel, so you will need to attach that variable to your viewModel. Like var self = this; self.model = [].... Declaring it just as a private var will not expose it to the knockout bindings and hence that will not work.
Once inside of a foreach binding, you don't need $index to access the objects inside of the array, just accessing them by their names is all you need. So <span data-bind="text: $data[$index].Main"></span> is replaced by <span data-bind="text: Main"></span>
You have another foreach nested inside the outer one, there too you have used $index accessor which really isn't necessary. To add to that <ul data-bind="foreach: subdetails in model.sub"> can easily be replaced with <ul data-bind="foreach: Sub"> (please also notice the typo in sub which should have been Sub)
Corrected fiddle here

Related

Knockout js binding on properties of nullable objects

I have a knockout.js page which loads data from an API and uses the knockout mapping plugin to turn the data into a parameter on the ViewModel.
The data contains nested objects e.g.
[{
id: 1,
targetField: {
id: 132,
name: 'Field ABC',
...
},
conditionalOperator: {
id: 8,
display: 'Less Than'
},
conditionalValue:13
},
...
]
Loaded into the page view model
var PageViewModel = function() {
...
this.allConditionLogic = ko.observableArray();
}
var pageViewModel = new PageViewModel();
$.get('api/...')
.done(function(data) {
pageViewModel.allConditionLogic(ko.mapping.fromJS(data));
});
The html contains bindings to the objects
<div data-bind="foreach: allConditionLogic">
<p>Field id <span data-bind="text: targetField().id"></span> <span data-bind="text: conditionalOperator().display"></span> <span data-bind="text: conditionalValue"></span></p>
</div>
This however errors as before the ajax call has returned, targetField and conditionalOperator are null.
It is possible to use extra span elements and the with binding which doesn't create the internal html if the bound object doesn't exist - e.g.
<p>Field id <span data-bind="with: targetField"><span data-bind="text: id"></span> <span data-bind="with: conditionalOperator"><span data-bind="text: display"></span></span> <span data-bind="text: conditionalValue"></span></p>
however this seams somewhat overkill. I could define a blank object in allConditionLogic with the correct fields, but that requires a lot more typing and needs updating if the API changes.
Is there a better way of getting this to work?
A simple solution could be to create a knockout observable variable and set it to false until you have the data returned by the API. Then wrap your div with that observable inside a ko if: binding -
var PageViewModel = function() {
this.allConditionLogic = ko.observableArray();
//Set it to false initially
this.hasAPIreturnedData = ko.observableArray(false);
}
var pageViewModel = new PageViewModel();
$.get('api/...')
.done(function(data) {
pageViewModel.allConditionLogic(ko.mapping.fromJS(data));
//make it true after data is returned and is transformed
pageViewModel.hasAPIreturnedData(true);
});
<!--ko if: hasAPIreturnedData -->
<div data-bind="foreach: allConditionLogic">
<p>Field id <span data-bind="text: targetField().id"></span> <span data-bind="text: conditionalOperator().display"></span> <span data-bind="text: conditionalValue"></span></p>
</div>
<!--/ko-->
There could be more elegant ways of handling this but that depends on a lot of things. As I said, this is the simplest solution I could think of :)

Looping through a javaScript object's array with angular

So I'm absolutely new to writing any type of code, I'm trying to build a simple website using AngularJS and I'm having a problem with looping through a controller's object array through a directive template. My goal is to output each array string into a separate list item element in the template,
for example in the controller I have
$scope.items = [
{
list: ["1", "2", "3"],
name: "whatever"
},
{
list: ["4", "5", "6", "7", "8"],
name: "whatever2"
}]
in the directive template I have written something like this
<h1>{{ directive.name }}</h1>
<ul id= "items"></ul>
<script>
for (var i = 0; i < directive.list[].length; i++) {
document.getElementById("items").innerHTML =
"<li>{{" + directive.list[i] + "}}</li>";
};
</script>
the name object retrieves correctly in index.html using the template but list object will not output the array, gave it my best shot, tried troubleshooting to get it right and I can't seem to figure it out, need help :( hope I'm being clear enough, this is my first attempt at creating anything and still trying to get familiar with all the jargon.
Here is a shot:
<div ng-repeat="i in items">
<h1>{{ i.name }}</h1>
<ul>
<li ng-repeat="item in i.list">{{i}} </li>
</ul>
</div>
And the description:
Basically you want to loop through all the items in your array <div ng-repeat.. then for each of those items you want to create the <ul>
It's simple -- replace your <script> with this:
<ul>
<li ng-repeat="item in items">{{ item.name }}
<ul id="items">
<li ng-repeat="list_item in item.list">{{ list_item }}</li>
</ul>
</li>
</ul>

KnockoutJS, how to update view model using HTML5 content editable?

I have the following code:
HTML:
<ul class="list" data-bind="foreach: list">
<li class="title" data-bind="text:title" contenteditable="true"></li>
<li class="item" data-bind="text:item" contenteditable="true"></li>
</ul>
<button type="button">Save</button>
JS:
var data = {
"list": [{
"title" : "title one",
"item" : "item one"
}]
}
var viewModel=
{
list : ko.observable(data.list)
};
ko.applyBindings(viewModel);
$("button").on("click", function(){
var vm = viewModel;
ko.applyBindings(vm);
var data = ko.toJSON(vm);
console.log(data);
});
However when I do this I get this error:
Uncaught Error: You cannot apply bindings multiple times to the same element.
knockout-3.1.0.js:58
What I would like to do is change the text of one of the items and have it save to the view model when I click the save button.
FIDDLE:
http://jsfiddle.net/sr4Fg/13/
There are few things.
only call applyBindings once, then ko will sync data for you.
you don't need to create a save button, ko sync data automatically.
your title and item are NOT ko.observable, hence ko has no way to auto-update it for you.
ko "text" binding is kind of one way binding, it only updates view when your value changes. You need to use "value" binding in an input tag to get two way binding.
right now, there is no existing ko binding supporting contenteditable. You may build a custom bindingHandler for it, but beware it's tricky to get contenteditable change event.
you list should be an observableArray.
Here is the working example with "value" binding: http://jsfiddle.net/sr4Fg/41/
<ul class="list" data-bind="foreach: list">
<li class="title"><input data-bind="value:title" /></li>
<li class="item"><input data-bind="value:item" /></li>
</ul>
<button type="button">Save</button>
var viewModel=
{
list : ko.observableArray([{
"title" : ko.observable("title one"),
"item" : ko.observable("item one")
}])
};
ko.applyBindings(viewModel);
$("button").on("click", function(){
var data = ko.toJSON(viewModel);
console.log(data);
});
Understand you may load JSON data from ajax call, it's tedious to change all the values into ko.observable. Try http://knockoutjs.com/documentation/plugins-mapping.html if you need.

Knockoutjs: how to filter a foreach binding without using if

Is there a way to implement the statements below using a custom binding, to eliminate the if-binding:
<div data-bind="foreach: $root.customersVM.customers">
#Html.Partial("_Customer")
<div data-bind="foreach: $root.ordersVM.orders">
<!-- ko if: customer() == $parent.id() -->
#Html.Partial("_Order")
<!-- /ko -->
</div>
</div>
Or put it in another way: Does someone know Way 2 in the answer to Knockout.js foreach: but only when comparison is true?
How about creating another computed or function that does the filtering and that you can iterate over instead of iterating over orders?
HTML
<div data-bind="with: filteredCustomers('smith')">
<span data-bind="text: name"></span>
</div>
<div data-bind="foreach: customers">
<span data-bind="text: name"></span>
</div>
<div data-bind="foreach: filteredOrders(4)">
<span data-bind="text: id"></span>
</div>
<div data-bind="foreach: orders">
<span data-bind="text: id"></span>
</div>
<button data-bind="click: function() {customers.push({name:'Smith'});}">Add customer</button>
<button data-bind="click: function() {orders.push({id:4});}">Add order</button>
Javascript:
var vm = {
customers: ko.observableArray([
{name: 'Smith'}, {name: 'Williams'}, {name: 'Brown'}, {name: 'Miller'}
]),
orders: ko.observableArray([
{id: 1}, {id: 2}, {id: 3}, {id: 4}, {id: 4}
])
};
// return first hit (unique ID)
vm.filteredCustomers = function(name) {
return ko.utils.arrayFirst(this.customers(), function(customer) {
return (customer.name.toLowerCase() === name.toLowerCase());
});
};
// return all hits
vm.filteredOrders = function(id) {
return ko.utils.arrayFilter(this.orders(), function(order) {
return (order.id === id);
});
};
ko.applyBindings(vm);
I think your best bet on performance would be to take the data from 2 different databases and put them together in the same viewmodel. For example, in your viewmodel in javascript, grab the customers first. Then grab the orders. Add an orders property to each customer and add an orders observablearray to it.
Your viewmodel is intended for use by the view. So its best to take the data, however it come sin, and make it work for the view. As you mention, the "if" will likely be a perf issue. Also, if you use a foreach in a function as you suggest in your comment it is needlessly looping through items when the observable arrays change. I prefer to get my viewmodel in order first, then the user interactions are fast.
2 cents :)

Master Details in Knockout JS

What am I doing wrong? I am trying to create a simple master details view a la the 'canonical MVVM' example.
Here's a simplified example in JSfiddle that doesn't work: http://jsfiddle.net/UJYXg/2/
I would expect to see the name of the selected 'item' in the textbox but instead it says 'observable'?
Here's my offending code:
var list = [ { name: "item 1"} , { name: "Item 2" }];
var viewModel = {
items : ko.observableArray(list),
selectedItem : ko.observable(),
}
viewModel.setItem = function(item) {
viewModel.selectedItem(item);
}
ko.applyBindings(viewModel);
And the HTML
<ul data-bind="foreach: items">
<li>
<button data-bind="click: $root.setItem, text:name"></button>
</li>
</ul>
<p>
<input data-bind="value:selectedItem.name" />
</p>
You are really close. Just need to do value: selectedItem().name or better use the with binding to change your scope. Also, the script that you are referencing is slightly out-of-date (in 2.0 click passes the data as the first arg).
Sample here: http://jsfiddle.net/rniemeyer/acUDH/

Categories