Function not able to access observableArray in knockout - javascript

I keep getting this error when I try to update an observableArray:
'Unable to get property 'removeAll' of undefined or null reference'
Here is my code:
var agilityFirmViewModel = {
test: ko.computed(function () {
return Pairs[0].Pairs;
}),
currentLocation: ko.observableArray(Pairs[0].Pairs[0].Location);
//function to update values in currentLocation
ChangeLocation: function (practiceGroup) {
if (practiceGroup === undefined) {
practiceGroup = '(All Practice Groups)';
}
for (var i = 0; i <= Pairs[0].Pairs.length; i++) {
if (practiceGroup.Department === Pairs[0].Pairs[i].Department) {
this.currentLocation.removeAll();
this.currentLocation.push(practiceGroup.Location[i]);
return root.currentLocation;
}
}
},
}
$(document).ready(function () {
ko.applyBindings(agilityFirmViewModel);
});
Assuming that it goes through the if statement inside the for loop everytime,
whenever it gets to
this.currentLocation.removeAll();
I get the error above saying that currentLocation is undefined. I know that currentLocation has data in it, so would could be causing this issue. It's probably something simple, but I can't seem to figure it out. Thank you.
Where i call ChangeLocation():
<li class="nav-sidebar-GreenMenu">
<a href="javascript:;" class="draggable-panel" draggable="true">
<i id="CategoryIcon" class="glyphicon glyphicon-user"></i>
<span class="sidebarTabName"><b>Practice Group</b></span><br />
<!--For some reason, changing the margin-left below will change the width of the entire sidebar-->
<span id="CurrentItem_TPG" class="currentItem" #*style="margin-left:64px;"*#>(All Practice Groups)</span>
<span id="DefaultItem_TPG" style="display:none;">(All Practice Groups)</span>
<i class="fa fa-chevron-left pull-right hidden-xs" data-bind=""></i>
<span class="arrow open" style="margin-top:-30px;"></span>
</a>
<ul class="sub-menu">
<!--ko foreach: test()-->
<li>
</li>
<!-- /ko -->
</ul>
</li>

The problem is the way you're using bind. The actual signature for bind is:
fun.bind(thisArg[, arg1[, arg2[, ...]]])
Meaning, the first argument determines what this means in the function and the rest sets the arguments to use when it's called. Instead, try changing your click handler to this:
$parent.ChangeLocation.bind($parent, $data);
This ensures that ChangeLocation is called with $parent === this and $data === practiceGroup

Related

Evaluation not working in ng-show()

I am trying to put a checkmark icon next to some text in a <li> tag within an ng-repeat loop.
HTML
<ul>
<li ng-repeat="nav in tabs.nav">
{{nav.name}}
<i class="fa fa-check my-checked-icon"
ng-show="$index===selectedIndex"
ng-click="selectNav(nav.index)"></i>
</li>
</ul>
JS
$scope.tabs = {
"navs":[
{
"name":"Lorem Ipsum",
"index":0
},
{
"name":"Adipiscing Elit",
"index":1
},
{
"name":"Ut Lbore et Dolore",
"index":2
}
]
};
var selectedIndex = 1;
$scope.selectNav = function(index){
$scope.selectedIndex = index;
};
CSS
.my-checked-icon{
display: none;
}
Both $index and selectedIndex print out the correct values when I put them in {{}}. And I've tried doing no braces as well as evaluating with ==.
I get the error
Error: [$parse:lexerr] Lexer Error: Unexpected next character at columns 0-0 [“] in expression [“$index==selectedIndex"].
I've seen other examples of people evaluating like this elsewhere. What is the problem? Is there a better way to do this inside of the controller somehow?
Try this
HTML
<ul>
<li ng-repeat="nav in tabs.navs" ng-click="selectNav(nav.index)">
{{nav.name}}
<i class="fa fa-check my-checked-icon"
ng-show="$index===selectedIndex"
></i>
</li>
</ul>
JS
var ncolor;
$scope.tabs = {
"navs":[
{
"name":"Lorem Ipsum",
"index":0
},
{
"name":"Adipiscing Elit",
"index":1
},
{
"name":"Ut Lbore et Dolore",
"index":2
}
]
};
$scope.selectedIndex = 1;
$scope.selectNav = function(index){
$scope.selectedIndex = index;
};
There is nothing wrong in your validation. you were declaring selectedIndex as variable. you should define as a $scope variable. So only then you will see the check icon on the right. And I moved the ng-click to the so that when you click it the check mark will come beside that. If you click a check mark it will stay at the same place as the index always remains the same and you cannot click the checks which are hidden

Angular.js run ng-show function after scope is updated

I have a poll application, in which a user can vote for an option in a given poll. in the html template, i use ng-show to show weather the user has voted for this option or this poll or if its an unvoted poll for the user:
<div data-ng-repeat="option in poll.poll_options" class="list-group-item">
<span data-ng-if="option.option_thumb == '2'" class="glyphicon glyphicon-thumbs-up"></span>
<span data-ng-if="option.option_thumb == '1'" class="glyphicon glyphicon-thumbs-down"></span>
<div data-ng-show="optionVoted(option,authentication.user._id)">
<span data-ng-bind="option.option_text"></span>
</div>
<div data-ng-hide="optionVoted(option,authentication.user._id)">
<div data-ng-show="pollVoted(poll._id,votes)">
<a data-ng-click="updateVote()">
<span data-ng-bind="option.option_text"></span> - update
</a>
</div>
<div data-ng-hide="pollVoted(poll._id,votes)">
<a data-ng-click="createVote(poll,option,authentication.user._id,$index)">
<span data-ng-bind="option.option_text"></span> - new
</a>
</div>
</div>
<span class="option-votes"> - {{option.votes.length}}</span>
</div>
these are the above mentioned functions to determine if the option / poll has been voted by the user:
// check if option is voted
$scope.optionVoted = function(option,userId){
for (var i = 0; i < option.votes.length; i++){
if (option.votes[i].user === userId){
return true;
}
}
};
//check if poll is voted
$scope.pollVoted = function(pollId,votes){
for (var i = 0; i < votes.length; i++){
if (votes[i].poll === pollId){
return true;
}
}
}
and here is the function to create a new vote:
// create a vote
$scope.createVote = function(poll,option,userId,index){
var vote = new Votes({
user:userId,
poll:poll._id,
poll_option:option._id
});
vote.poll_option_id = option._id;
vote.$save(function(vote){
option.votes.push(vote);
$scope.$apply();
}, function(errorResponse) {
$scope.error = errorResponse.data.message;
});
}
what happens on the front end, is that the option which has been now voted is updated (not showing an a tag anymore). what i need, is that the other options in the poll will update as well, and now instead of create() they will show update(), without refreshing the page.
how can I get the other html DOM elements of options in the poll to update?
In html, replace the functions in ng-show by an object property :
ng-show="option.voted", for example.
and update option.voted in createVote function.
(adapt this with userId etc.)
Make sure you are pushing the new vote onto the correct object in the scope. It looks like you are displaying data from $scope.poll.poll_options in your view, but you are adding to options.votes in your createVote function.

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

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

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.]

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.

Categories