Durandal 2.0 - Update the value of an observable in the view - javascript

I'm new to Durandal, my question has probably a very simple issue.
I load a list into a dropdown and the current value on the link which display the dropdown,
And the value of the link which display the dropdown is not updated correctly when an other value is selected.
But actually, I can't set the value of the observable in the select function.
View Model
var self = this;
self.system = require('durandal/system');
IPsKeys: ko.observableArray([]),
ipKeys: ko.observable(""),
activate: function (context) {
var that = this;
that.IPsKeys([]);
that.ipKeys("");
return $.when(
service.getIPSbyClientId(context.clientId).then(function (json) {
$.each(json, function (Index, Value) {
var ClientLobUWYear = {
NameLob: Value.LineOfBusiness.Name,
NameUWYear: Value.UnderwritingYear
};
that.IPsKeys().push(ClientLobUWYear);
// HERE MY VALUE IS GOOD UPDATING AND THE BINDING WORK
if (Index=== 0) {
that.ipKeys(ClientLobUWYear);
}
});
})
).then(function () {
//do some other datacontext calls for stuff used directly and only in view1
});
},
select: function (item) {
this.ipKeys = {
IdClient: item.IdClient,
IdLob: item.IdLob,
NameLob: item.NameLob,
NameUWYear: item.NameUWYear
};
/** PROBLEMS HERE **/
/** Uncaught TypeError: undefined is not a function **/
this.ipKeys(ClientLobUWYear);
},
View
<a id="select_lob-UWYear" class="dropdown-toggle" data-toggle="dropdown" href="#">
<span class="controls_value" data-bind="text: ipKeys().NameLob">ALOB</span>
<span class="controls_value" data-bind="text: ipKeys().NameUWYear">AYEAR</span>
</a>
<ul id="dropdown_year" class="dropdown-menu" data-bind="foreach: IPsKeys().sort(sortByLobYear)">
<li>
<a href="#" data-bind="click: $parent.select">
<span class="controls_value" data-bind="text: NameLob">Cargo</span>
<span class="controls_value" data-bind="text: NameUWYear">2014</span>
</a>
</li>
</ul>
Thanks a lot

The way you update an observable is like this:
var someObservable = ko.observable(""); //setting to "";
someObservable("Something else"); //updating to "Something else"
Not like this (which you are doing above)
var someObservable = ko.observable(""); //setting to "";
someObservable = "Something else";
This is overwriting someObservable with a string of value "Something else" and so is no longer an observable which is why it will not update the ui.
[JS Fiddle showing how to set observables.]

Related

Adding item to knock out view model , is not updating the view

I have a ViewModel which I am binding to view list item.
var MyViewModel = function() {
var self = this;
self.addItems = function(vm) {
vm.inventoryItems.push('New Item');
}
};
var myVM= new MyViewModel();
ko.applyBindings(myVM);
The view model has a property called inventoryItems (which is from a service).
I am bidning that to view using ,
<ul data-bind="foreach:inventoryItems">
<li>
<input class="form-control" type="text" data-bind="value: $data" />
</li>
</ul>
<div class="text-right">
<a data-bind="click: $parent.addItems">+ Add more</a>
</div>
Now, the items that are already in the collection , inventoryItems are getting rendered fine.
When I am adding a new item using, I can see the items being added via console, but the view is not getting updated!
self.addItems = function(vm) {
vm.inventoryItems.push('New Item');
}
The below code snippet will make your inventoryItems observable
var MyViewModel = function () {
var self = this;
self.inventoryItems = ko.observableArray();
self.addItems = function (vm) {
vm.inventories.push('New Item');
self.inventoryItems(vm.inventories);
}
};
var myVM = new MyViewModel();
ko.applyBindings(myVM);

Knockout: Can't get specific value from observableArray

I created array with data about some music albums in viewmodel. I can use this array in foreach loop (for example) but I can't display only one value from this array.
My viewmodel:
function CD(data) {
this.Author = ko.observable(data.Author);
this.Title = ko.observable(data.Title);
this.Price = ko.observable(data.Price);
this.Label = ko.observable(data.Label);
this.Id = ko.observable(data.Id);
}
function NewsListViewModel() {
var self = this;
self.newsList = ko.observableArray([]);
$.getJSON("/music/news", function (allData) {
var mapped = $.map(allData, function (item) { return new CD(item) });
self.newsList(mapped);
});
self.oneObject = ko.computed(function () {
return self.newsList()[0].Title;
});
}
$(document).ready(function () {
ko.applyBindings(new NewsListViewModel());
});
And how I used it in html:
<ul data-bind="foreach: newsList">
<li data-bind="text: Title"></li>
<li data-bind="text: Author"></li>
<li data-bind="text: Price"></li>
<li data-bind="text: Label"></li>
</ul>
This works perfectly. But if I trying additionally display one value:
<ul data-bind="foreach: newsList">
<li data-bind="text: Title"></li>
<li data-bind="text: Author"></li>
<li data-bind="text: Price"></li>
<li data-bind="text: Label"></li>
</ul>
...
<p data-bind="text: oneObject"</p>
it dosen't working, firebug thrown error:
Type error: self.newsList()[0].Title is undefined
Why I cannot get one specific value but I can display whole list?
PS. Code <p data-bind="text: newsList()[0].Title"</p> didn't works too.
You get the error because $.getJSON is asynchronous.
So when your computed is declared your items are not necessary ready in the newsList so there is no first element what you could access.
So you need to check in your computed that your newsList is not empty (anyway your server could also return an empty list so it is always good to have this check) and only then access the first element:
self.oneObject = ko.computed(function () {
if (self.newsList().length > 0)
return self.newsList()[0].Title();
return 'no title yet';
});
Note: you need also write .Title() to get its value because Title is an observable.
I can see a few problems with your code:
$.getJSON is asynchronous so self.newsList is empty when it's accessed by Knockout for the first time. Which in turn means that self.newsList[0] is undefined.
return self.newsList()[0].Title; in the computed observable doesn't return the value you want it to return. It should be return self.newsList()[0].Title();
You don't have to reinvent the wheel, there is the mapping plugin which you can use to map the data: http://knockoutjs.com/documentation/plugins-mapping.html

Minimise memory usage of empty observableArrays in knockout

I have a simple webpage with a large list of products (20,000+). When you can click on a product, it will load (via AJAX) a list of colors and display them inline. Html...
<div data-bind="foreach: products">
<span data-bind="click: $root.loadColors($data), text: $name"></span>
<ul data-bind="foreach: colors">
<li data-bind="text:$data" />
</ul
</div>
Shop view model:
function shopViewModel()
{
var self = this;
self.products = ko.observableArray([]);
self.loadColors = function(product)
{
var data = GetColorsByAjax();
product.colors(data);
}
}
Product view Model:
function productModel(data)
{
var self = this;
self.name = data.name;
self.colors = ko.observableArray([]);
}
When I have 20,000+ products, it uses a lot of memory. Each product has a colors array, which is always empty/null, until the user clicks on it, but it still uses a lot of memory.
Ideally, I'd like to remove the colors observableArray and somehow create it dynamically when user clicks on the product. Or separate it into a new viewModel.
I want to eliminate the empty observableArrays to minimise memory, but can't figure out how it do it.
I would use one of Knockout's control-flow bindings (if, with) to only bind the colors:foreach when there is actually a colors property on the productModel().
HTML:
<div data-bind="foreach: products">
<span data-bind="click: $root.loadColors($data), text: $name"></span>
<div data-bind="if: hasColors">
<ul data-bind="foreach: colors">
<li data-bind="text:$data" />
</ul>
</div>
</div>
Product View Model:
function productModel(data)
{
var self = this;
self.name = data.name;
self.hasColors = ko.observable(false);
self.colors = null;
}
Shop View Model
function shopViewModel()
{
var self = this;
self.products = ko.observableArray([]);
self.loadColors = function(product)
{
var data = GetColorsByAjax();
if(product.colors == null) {
product.colors = ko.observableArray(data);
product.hasColors(true);
} else {
product.colors(data);
}
}
}
You don't have to store an empty observable array: you can default to undefined and Knockout will treat it as an empty array in a foreach binding.
Here's a demonstration: http://jsfiddle.net/zm62T/

How to handle nested menu in knockout viewmodel

I am using knockoutjs in my project. I have a scenario where I have to create a nested menu in my viewmodel, which I did like this:
self.menu = [
{
name: 'Services',
sub: [{ name: 'Service-A' }, { name: 'Service-B' }]
},
// etc
];
self.chosenMenu = ko.observable();
self.goToMenu = function (main, sub) {
var selectedMenu = {
main: main,
sub: sub
};
self.chosenMenu(selectedMenu);
};
My View:
<ul class="nav navbar-nav menuitems col-md-8" data-bind="foreach: menu">
<li class="dropdown">
<a href="#" class="dropdown-toggle" data-toggle="dropdown">
<span data-bind="text: name"></span></a>
<ul class="dropdown-menu" role="menu" data-bind="foreach: sub">
<li>
<a href="javascript:void(0);" data-bind="text: name,
click: function() { $root.goToMenu($parent, $data); }">
</a>
</li>
</ul>
</li>
</ul>
However, I feel that this approach of creating nested menu is not good, because suppose if I want to go on any menu item programmatically then with his approach it is not possible?
Can anybody please suggest me the good approach to handle this kind of scenario?
Make sure each menu item has a unique identifier. For the example data you've given name will suffice, but you may have to add a fullPath property to menu item view models.
Your goToMenu function can now take just one parameter: uniqueMenuIdentifier, and recurse through all menu items to find the correct one, like this:
function findMenuItem(menuList, uniqueMenuIdentifier) {
for (var i = 0; i < menuList.length; i++) {
if (menuList[i].name === uniqueMenuIdentifier) {
return menuList[i];
}
if (!!menuList[i].sub) {
var subItem = findMenuItem(menuList[i].sub, uniqueMenuIdentifier);
if (!!subItem) {
return subItem;
}
}
}
return null;
}
self.goToMenu = function (menuItem) {
var uniqueMenuIdentifier = menuItem.name;
var item = findMenuItem(self.menu, uniqueMenuIdentifier);
self.chosenMenu(item);
}
This allows for a much simpler binding in the anchor tag:
<a href="javascript:void(0);" data-bind="text: name, click: $root.goToMenu">
See this fiddle for a demo.
From this you can also guess that it's even possible to set the chosenMenu directly:
// No longer needed:
//function findMenuItem(menuList, uniqueMenuIdentifier) { }
self.goToMenu = function (menuItem) {
self.chosenMenu(menuItem);
}
See this fiddle for a demo.
I ran into a similar scenario yesterday. You can have a look at my solution on
Is there any knockout plugin for showing a nested context menu?. The main point is that I've used the template binding to be able to construct a hierarchical menu of any depth.

how to do data-bind for complex model knockout

I am a newbee to knockout, I'm trying to move from the MVC ViewModel binding.
I have a complex model:
SearchStudentsModel which has 2 properties
Collection of Students (Subset of students)
Number of Students overall
Note that the length of the collection isn't equal to the number overall.
I need to implement a search functionality
Student will have all the regular properties plus IsActive indicator.
I use ul and li tags to data-bind the details.
The search screen should facilitate the user in marking the active flag with an indicator (on and off) and immediately data should be saved in the database.
All the examples I referred to talk about only one level of model. I have a SearchStudent model and within that I have a collection of students.
How should the binding be for this hierarchy of models?
I have refactored your jsFiddle. Hoping you can now understand knockoutJS better. It is not your whole page/Knockout, but I think with this snippet your problem can be solved.
the markup:
<button id="searchEmployees" type="button" data-bind="click: search">Search</button>
<li data-bind="foreach: Employees">
ID: <span data-bind="text: Id"></span><br/>
Name: <span data-bind="text: Name"></span><br/>
Active: <span data-bind="click: ToggleActivation, text: IsActive"></span> <-- click<br/>
</li>
<span data-bind="text: Employees().length"></span> of
<span data-bind="text: AllEmployees().length"></span>
the js/viewmodel
function Employee(id, name, isactive){
var self = this;
self.IsActive = ko.observable(isactive);
self.Id = ko.observable(id);
self.Name = ko.observable(name);
self.ToggleActivation = function () {
if(self.IsActive() === true)
self.IsActive(false);
else
self.IsActive(true);
};
}
var searchEmployeeViewModel = function () {
var self = this;
self.Employees = ko.observableArray([]);
self.AllEmployees = ko.observableArray([]);
self.search = function () {
//Ajax call to populate Employees - foreach on onsuccess
var employee1 = new Employee(2, "Jane Doe", true);
var employee2 = new Employee(3, "Kyle Doe", false);
var employee3 = new Employee(4, "Tyra Doe", false);
var employee = new Employee(1, "John Doe", true);
self.AllEmployees.push(employee);
self.AllEmployees.push(employee1);
self.AllEmployees.push(employee2);
self.AllEmployees.push(employee3);
self.Employees.push(employee);
}
}
$(document).ready(function () {
ko.applyBindings(new searchEmployeeViewModel());
});
or you can simply use my jsFiddle if you do not like reading my code here ;)

Categories