Knockout search in observable array - javascript

I try to search by name in observable array. Here's my code:
<input class="form-control" data-bind="value: Query, valueUpdate: 'keyup'" autocomplete="off">
And my code in ViewModel
viewModel.Query = ko.observable('');
viewModel.search = function(value) {
viewModel.TestList.removeAll();
for (var x in viewModel.TestList) {
if (viewModel.TestList[x].Name.toLowerCase().indexOf(value.toLowerCase()) >= 0) {
viewModel.TestList.push(viewModel.TestList[x]);
}
}
}
viewModel.Query.subscribe(viewModel.search);
First: I would like to search by name string.
Two: Is there any other sollutions to not remove all elements from the view? I mean when query string is empty, there should be all list again.
Now I have error message:
TypeError: viewModel.TestList[x].Name is undefined

Use a computed observable array to show search results, along these lines:
var viewModel = {
items: [ { Name: "Apple part" }, { Name: "Apple sauce" }, { Name: "Apple juice" }, { Name: "Pear juice" }, { Name: "Pear mush" }, { Name: "Something different" } ]
};
viewModel.Query = ko.observable('');
viewModel.searchResults = ko.computed(function() {
var q = viewModel.Query();
return viewModel.items.filter(function(i) {
return i.Name.toLowerCase().indexOf(q) >= 0;
});
});
ko.applyBindings(viewModel);
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>
<input class="form-control" data-bind="value: Query, valueUpdate: 'keyup'" autocomplete="off">
<h3>Search results:</h3>
<ul data-bind="foreach: searchResults">
<li data-bind="text: Name"></li>
</ul>
<h3>All items:</h3>
<ul data-bind="foreach: items">
<li data-bind="text: Name"></li>
</ul>
This also removes the need for a subscription or seperate function.
This example utilizes:
A regular observableArray for storing all items (this list is always the same, regardless of your search query);
A read-only computed observable which returns a filtered array of items that match your query;
The array filter method (you call it on the observableArray, but KO just forwards it to the underlying array);
As you can see in the example, items will always contain all objects, and searchResults is just a filtered read-only view on that array.

Related

knockout observable array binding to view when there is a delay in assigning value not happening

I have a knockout observable array whose value assignment changes after a set value of time but do not see this reflecting in the view. Could someone tell where I am doing it wrong? I would expect the output to show
• GRE 1111
• TOEFL 111
but it shows
• GRE2 222
• TOEFL2 22
jsFiddle link: https://jsfiddle.net/4r37x9y5/
HTML:
console.clear();
function viewModel() {
this.plans = ko.observableArray([]);
var plans1 = [
{ id: 'GRE', count: '1111' },
{ id: 'TOEFL', count: '111' },
];
var plans2 = [
{ id: 'GRE2', count: '222' },
{ id: 'TOEFL2', count: '22' },
];
this.plans = plans2;
//this.plans = plans1;
setTimeout(function(){
console.log("In timeout before assigning plans");
this.plans = plans1;
console.log(this.plans);
}, 2000);
}
ko.applyBindings(viewModel());
// The above line equals:
// viewModel(); // updates window object and returns null!
// ko.applyBindings(); // binds window object to body!
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.2/knockout-min.js"></script>
<div class="panel panel-default">
<ul data-bind="foreach: plans" class="list-group">
<li class="list-group-item">
<span data-bind="text: id"></span>
<span data-bind="text: count"></span>
</li>
</ul>
</div>
There are couple of issues here. As mentioned by you in the comments, you are not binding an object with observables. You are simply adding a global variable plans. If knockout can't find a property in the viewModel, it will use the window object's property. That's why it works the first time
You need to change viewModel as a constructor function and use new viewModel() to create an object or an instance.
observables should be read and updated by calling them as functions. So, this.plans(plans1). If you set this.plans = plans2, it will simply overwrite the observable with a simple array without the subscribers to update the UI when the property changes
You need to use correct this inside setTimeout. Either by creating a self = this variable outside or using an arrow function as a callback
function viewModel() {
this.plans = ko.observableArray([]);
var plans1 = [{ id: "GRE", count: "1" }, { id: "TOEFL", count: "1" }];
var plans2 = [{ id: "GRE2", count: "2" }, { id: "TOEFL2", count: "2" }];
this.plans(plans2) // call it like a function
setTimeout(() => {
console.log("In timeout before assigning plans");
this.plans(plans1)
}, 2000);
}
ko.applyBindings(new viewModel()); // new keyword to create an object
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.2/knockout-min.js"></script>
<ul data-bind="foreach: plans">
<li>
<span data-bind="text: id"></span>
<span data-bind="text: count"></span>
</li>
</ul>

Knockout observable array doesn't re-render my list

I need some help here: https://jsfiddle.net/vhaurnpw/
I want to do a simple list, which is filterable by the input on top and updates itself..
JS/Knockout:
var viewModel = {
query: ko.observable(''),
places: ko.observable(data),
search: function(value) {
viewModel.places = [];
console.log(value)
for (var i = 0; i < data.length; i++) {
if(data[i].name.toLowerCase().indexOf(value.toLowerCase()) >= 0) {
viewModel.places.push(data[i]);
}
console.log(viewModel.places);
}
}
};
viewModel.query.subscribe(viewModel.search);
ko.applyBindings(viewModel);
HTML:
<form acion="#" data-bind="submit: search">
<input placeholder="Search" type="search" name="q" data-bind="value: query, valueUpdate: 'keyup'" autocomplete="off">
</form>
<ul data-bind="foreach: places">
<li>
<span data-bind="text: name"></span>
</li>
</ul>
List will be rendered, but it when you type something, it doesn't show you the result.
Instead when you lookup the console, you will see the console.log and it updates just fine!
so how do i refresh my HTML? :(
There are following issues in your code.
places needs to be an ObservableArray and not an Observable so that you can track the addition/removal from the Observable Array. So, change code From places: ko.observable(data) To places: ko.observableArray(data),
viewModel.places is function, so when you assign another value like viewModel.places = [], it is assigning an empty array to the viewModel.places. In order to modify the value of the viewModel.places, you need to call it as a function like viewModel.places([]);
Note: Your code doesn't add the data back in case the textbox is cleared, I hope you got the solution to the problem and you can resolve this issue as well.
Complete Working Code:
var data = [
{ name: 'Isartor'},
{ name: 'Sendlinger Tor'},
{ name: 'Marienplatz'},
{ name: 'Stachus'},
{ name: 'Bayerischer Rundfunk'},
{ name: 'Google München'},
{ name: 'Viktualienmarkt'},
{ name: 'Museumsinsel'},
{ name: 'Tierpark Hellabrunn'},
{ name: 'Allianz Arena'},
{ name: 'Olympia-Park'},
{ name: 'Flaucher-Insel'}
];
var viewModel = {
query: ko.observable(''),
places: ko.observableArray(data),
search: function(value) {
viewModel.places([]);
console.log(value)
for (var i = 0; i < data.length; i++) {
if(data[i].name.toLowerCase().indexOf(value.toLowerCase()) >= 0) {
viewModel.places.push(data[i]);
}
console.log(viewModel.places);
}
}
};
viewModel.query.subscribe(viewModel.search);
ko.applyBindings(viewModel);
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>
<form acion="#" data-bind="submit: search">
<input placeholder="Search" type="search" name="q" data-bind="value: query, valueUpdate: 'keyup'" autocomplete="off">
</form>
<ul data-bind="foreach: places">
<li>
<span data-bind="text: name">asd</span>
</li>
</ul>

ObservableArray binding won't update

Edit: JSFiddle with comments
I'm developing my first SPA using knockoutjs. My situation is:
I have a list of items being displayed from which the user can select an item
With an item selected, the user can make changes to the selected item
After confirming the changes, the SPA sends the updated data to the web api
However, the list displaying all my entries does not reflect the updates made to the item
I created a simple fiddle.js (see here). It shows my problem better than 1000 words. I left out pagination logic for simplicity, but the observable for my list needs to be a computed for various reasons.
ViewMode.
var ViewModel = function() {
var self = this;
self.selectedItem = ko.observable();
self.items = ko.observableArray([
{
name: "Item A",
price: "12.99"
},
{
name: "Item B",
price: "13.99"
},
{
name: "Item C",
price: "90.99"
}]);
self.paginated = ko.computed(function() {
// This is where I do some pagination and filtering to the content
// It's left out here for simplicity. The binding for the list needs
// to be a computed though.
return self.items();
});
self.selectItem = function(item) {
self.selectedItem(item);
};
self.save = function(item) {
// Sending data to web api...
// After the saving, the displaying list does not update to reflect the changes
// I have made. However, switching entries and checking the changed item shows
// that my changes have been saved and are stored in the observable.
}
};
ko.applyBindings(new ViewModel());
View
<!-- ko foreach: paginated -->
<br />
<!-- /ko -->
<br />
<br />
<div data-bind="visible: selectedItem">
<!-- ko with: selectedItem -->
<form>
<input type="text" data-bind="value: name" />
<input type="text" data-bind="value: price" />
<br />
<button type="button" data-bind="click: $parent.save">Save</button>
</form>
<!-- /ko -->
</div>
I hope you can help me out, I don't want to reload all the data from the server for the sake of performance and speed.
you have to make the properties of the objects in your array observable properties in order to reflect the changes to the UI.
self.items = ko.observableArray([
{
name: ko.observable("Item A"),
price: ko.observable("12.99")
},
{
name: ko.observable("Item B"),
price: ko.observable("13.99")
},
{
name: ko.observable("Item C"),
price: ko.observable("90.99")
}]);

How to filter objects in array by category key in Angular ng-repeat

I'm trying to use Angular filter to display only sorted tags by category
Example of a tag object in tags array:
{
term: term,
id: id,
category: category
}
The ng-repeat tags:
<li ng-repeat="(k, m) in tags | filter: filterTags | orderBy:predicate:reverse"
ng-class="{'selected': m.selected}"
ng-click="selectTag(m)">
<div class="tag">{{m.term}}</div>
</li>
The sort by category radio buttons:
<div class="category-selection">
<ul>
<li>
<input type="radio" ng-model="catSort" name="brand" value="brand">
Brand
</li>
<li>
<input type="radio" ng-model="catSort" name="client" value="client">
Client
</li>
In the sort radio button directive controller:
// Detect category sort
// Then apply the value to the filter function:
$scope.$watch('catSort', function(value) {
console.log(value);
tagsPanel = ScopeFactory.getScope('tagsPanel');
tagsPanel.filterTags(value);
});
I found out that filter is has it's own Angular module, so my question is, how do I get the category strings into this filter?
.filter('filterTags', function() {
return function(tags, category) {
return tags;
};
});
Here is where I capture the new category, how would I send the value into the filter above?
$scope.$watch('catSort', function(value) {
console.log(value);
});
If I got it right. You want to filter your tag object array by category.
You can call a scope method and return true if it matches the currently selected category. The parameter for this method will be a tag object of your ng-repeat. So you can do a check like return tag.category == $scope.catSort;
Please have a look at the demo below and here at jsFiddle.
(I've took sport categories just to have some dummy data.)
angular.module('myApp', [])
.controller('mainCtrl', function ($scope) {
$scope.catSort = "football";
$scope.tags = [{
term: 'foot1',
id: 'id',
category: 'football'
}, {
term: 'foot2',
id: 'id2',
category: 'football'
}, {
term: 'base1',
id: 'id',
category: 'baseball'
}, {
term: 'base2',
id: 'id2',
category: 'baseball'
}, ];
$scope.filterTags = function (tag) {
//console.log(tag, $scope.catSort);
return tag.category == $scope.catSort;
};
});
ul {
list-style-type: none;
}
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="myApp" ng-controller="mainCtrl">Filter by category:
<div class="category-selection">
<ul>
<li>
<input type="radio" ng-model="catSort" name="football" value="football" />Football</li>
<li>
<input type="radio" ng-model="catSort" name="baseball" value="baseball" />Baseball</li>
</ul>
</div>Teams:
<ul>
<li ng-repeat="(index, tag) in tags | filter: filterTags" ng-class="{'selected': tag.selected}" ng-click="selectTag(tag)">
<div class="tag">{{tag.term}}</div>
</li>
</ul>
</div>

Item selection MVC view with KnockoutJS

I am trying to implement a generic ASP.net MVC view. The UI should display a list of available and selected items loading data (basically list of string) from server. User can make changes into the list i.e. can select new items from available item list and also can remove items from selected list.
I wanted to do it using KnockoutJS as to take advantage of binding.
I manage to complete it upto the point everything is working except showing selected item as checked when the view is initialized in available list. E.g. As Shown Here
I tried various options (using template (closest to what I want to achieve), Checked attr, possible options), the issue is if I manage to display item checked some other functionality breaks. Tried defining a template but could not get it to work in my case.
HTML:
<div class='moverBoxOuter'>
<div id='contactsList'>
<span data-bind="visible: availableItems().length > 0">Available countries: </span>
<ul data-bind="foreach: availableItems, visible: availableItems().length > 0">
<li>
<input type="checkbox" data-bind="checkedValue: $data, checked: $root.selectedItems" />
<span data-bind="text: title"></span>
</li>
</ul>
<span data-bind="visible: selectedItems().length > 0">Selected countries: </span>
<ul data-bind="foreach: selectedItems, visible: selectedItems().length > 0">
<li>
<span data-bind="text: title"></span>
Delete
</li>
</ul>
</div>
JS:
var initialData = [
{
availableItems: [
{ title: "US", isSelected: true },
{ title: "Canada", isSelected: false },
{ title: "India", isSelected: false }]
},
{
selectedItems: [
{ "title": "US" },
{ "title": "Canada" }
]
}
];
function Item(titleText, isSelected) {
this.title = ko.observable(titleText);
this.isSelected = ko.observable(isSelected);
}
var SelectableItemViewModel = function (items) {
// Data
var self = this;
self.availableItems = ko.observableArray(ko.utils.arrayMap(items[0].availableItems, function (item) {
return new Item(item.title, item.isSelected);
}));
self.selectedItems = ko.observableArray(ko.utils.arrayMap(items[1].selectedItems, function (item) {
return new Item(item.title, item.isSelected);
}));
// Operations
self.selectItem = function (item) {
self.selectedItems.push(item);
item.isSelected(!item.isSelected());
};
self.removeItem = function (removedItem) {
self.selectedItems.remove(removedItem);
$.each(self.availableItems, function (item) {
if (item.title === removedItem.title) {
item.isSelected = false;
}
});
};
}
var vm = new SelectableItemViewModel(initialData);
$(document).ready(function () {
ko.applyBindings(vm);
});
Could you please help, see jsfiddle below:
http://jsfiddle.net/sbirthare/KR4a6/6/
**Update: Follow up question below **
Its followup question:
I need to add a combobox on same UI e.g. for US state. The available items are counties, based on user selection in state combo I need to filter out counties. I am getting data from server using AJAX and its all successful BUT the displayed list is not refreshing. I was expecting having binding setup correctly, if we change the observable array in viewmodel, the UI should change. I tried forcing change to availableItems but it just display all items. Please see if you can spot the problem in below code where I am updating ViewModel observable array.
function multiselect_change() {
console.log("event: openmultiselect_change");
var selectedState = $("#stateDropdownSelect").val();
var propertyName = $("#PropertyName").val();
var searchId = #Model.SearchId;
var items;
var model = { propertyName: propertyName, searchId: searchId, stateName: selectedState };
$.ajax({
url: '#Url.Action("GetFilterValues", "Search")',
contentType: 'application/json; charset=utf-8',
type: 'POST',
dataType: 'html',
data: JSON.stringify(model)
})
.success(function(result) {
debugger;
items = JSON.parse(result);
vm.availableItems(items.AvailableItems);
//vm.availableItems.valueHasMutated();
//var item = document.getElementById('availableItemId');
//ko.cleanNode(item);
//ko.applyBindings(vm, item);
vm.filter(selectedState);
})
.error(function(xhr, status) {
alert(status);
});
}
As user3426870 mentioned, you need to change the value you passed to the checked binding to boolean.
<input type="checkbox" data-bind="checkedValue: $data, checked: isSelected" />
Also, I don't think you need to have selectedItems in the initial data.
Instead in the viewModel, you can do something like:
self.selectedItems = ko.computed(function() {
return ko.utils.arrayFilter(self.availableItems(), function (item) {
return item.isSelected();
});
});
It's because you give an array to the binding checked while it's supposed to be a value comparable to true or false (like undefind or an empty string).
I would use a function checking if the $data is in your array and returning a boolean to your binding.
Something like that!

Categories