Knockoutjs arrayFilter multiple dropdowns - javascript

I have a question regarding arrayFilter in knockoutjs, how would i go about filtering my list with 2 different dropdowns which whould be related so if i have choosen 1 type of building but no area i should be shown all of that type of buildings, however if i where to choose a building option and an area option the filtering should account for that, ive been working on a prototype now for 2 days but cant figure out how to return the correct item in the arrayfilter.
http://jsfiddle.net/vGg2h/138/
Currently i made all my models and pastin in data via the viewmodel, and i got a filtered list hooked up, however i dont understand how to return the correct item back through the foreach filter and the arrayFilter, this is where it gets abit blurry.
self.filteredList = ko.computed(function () {
var filters = [];
filters.push(self.selectedBuilding());
filters.push(self.selectedArea());
var currentList = [];
ko.utils.arrayForEach(filters, function (filter) {
if (typeof filter !== "undefined") {
ko.utils.arrayFilter(self.products(), function (item) {
if (filter.id == item.areaId || filter.value == item.buildingId) {
currentList.push(item);
}
});
}
});
return currentList;
});
Thanks in advance for any answers!

You have two problems:
you are not correctly using ko.utils.arrayFilter: you have to return true or false depending on whether and item should be included in the end result or not. So you should not build your result inside the arrayFilter
you are always starting form the full list and not applying the filters one after the other, but incorrectly build the result in the arrayFilter which lead to combining your filters with OR and not with AND as you originally wanted
Your fixed code would like this:
self.filteredList = ko.computed(function () {
var filters = [];
filters.push(self.selectedBuilding());
filters.push(self.selectedArea());
var currentList = self.products();
ko.utils.arrayForEach(filters, function (filter) {
if (typeof filter !== "undefined") {
currentList = ko.utils.arrayFilter(currentList, function (item) {
if (filter.id == item.areaId || filter.value == item.buildingId) {
return true;
}
});
}
});
return currentList;
});
Demo JSFiddle
Two better see the AND filtering with reusing the same list you can rewrite your code to do the filtering in two separate steps:
self.filteredList = ko.computed(function () {
var currentList = self.products();
if (self.selectedBuilding())
{
currentList = ko.utils.arrayFilter(currentList, function(item) {
return self.selectedBuilding().value == item.buildingId;
});
}
if (self.selectedArea())
{
currentList = ko.utils.arrayFilter(currentList, function(item) {
return self.selectedArea().id == item.areaId;
});
}
return currentList;
});
In this code it is more clearer that you start from the full list and then apply the different filters one by one, further and further filtering the original list.
Demo JSFiddle
Note: if you initially want to start with an empty list (like in your original code) then you can just return an empty array if all of your filters are empty:
self.filteredList = ko.computed(function () {
if (!self.selectedBuilding() && !self.selectedArea())
return [];
//...
};
Demo JSFiddle.

Related

React - Filtering returns wrong rows

It's driving me crazy. I've created a list with several entries. I added a filtering function, which seems to work fine. I've checked the number of results returned, but somehow it just showing the result number beginning at the first row.
For explanation:
Let's assume I search for "Zonen" and my filter function returns 4 rows with ID 23, 25, 59 and 60, the rows with ID's 1,2,3 and 4 are displayed. What I'm doing wrong!?
...
render() {
let filteredList = this.state.freights.filter((freight) => {
let search = this.state.search.toLowerCase();
var values = Object.keys(freight).map(function(itm) { return freight[itm]; });
var flag = false;
values.forEach((val) => {
if(val != undefined && typeof val === 'object') {
var objval = Object.keys(val).map(function(objitm) { return val[objitm]; });
objval.forEach((objvalue) => {
if(objvalue != undefined && objvalue.toString().toLowerCase().indexOf(search) > -1) {
flag = true;
return;
}
});
}
else {
if(val != undefined && val.toString().toLowerCase().indexOf(search) > -1) {
flag = true;
return;
}
}
});
if(flag)
return freight;
});
...
<tbody>
{
filteredList.map((freight)=> {
return (
<Freight freight={freight} onClick={this.handleFreightClick.bind(this)} key={freight.id} />
);
})
}
</tbody>
...
UPDATE
freights is loaded and filled via AJAX JSON result. One object of freights looks like this:
I have a textbox where a user can perform a search. This search should return all freight objects which properties contain the search string.
The filter is so complex, because I want to also to search in sub-objects of freight. Maybe there is a more simple way?
"Zones" was just an example for a search string the user can search for.
Now that your intentions are clearer, I suggest this much less complex solution.
First, you can write a recursive utility fn to get all values of all keys in an n-depth object. Like this, for example (I'm using lodash's utility fn isObject there):
const getAllValues = (obj) => {
return Object.keys(obj).reduce(function(a, b) {
const keyValue = obj[b];
if (_.isObject(keyValue)){
return a.concat(getAllValues(keyValue));
} else {
return a.concat(keyValue);
}
}, []);
}
Now that you have an array of all object's values, it makes your filter very simple:
let filteredList = this.state.freights.filter((freightItem) => {
const allItemValues = getAllValues(freightItem);
return allItemValues.includes(this.state.search);
});
That should be it. If something is not working, gimme a shout.
I have found the solution why the "wrong" freight entries are displayed.
I needed to add in freight component the componentWillReceiveProps method:
componentWillReceiveProps(nextProps) {
if(nextProps.freight) {
this.setState({
freight: nextProps.freight
});
}
}
Then everything worked fine.

Angularjs foreach only returns one object

This could be something simple and I'm overlooking it. But I'm building out a filter by categories and once a user clicks a category it updates a scope (my instance $scope.productStuff) and display the objects accordingly. My problem is when I click the category it gives me back the mulitple objects in my console. Then I look at the dom and it only shows one object (and it's the last object) instead all the objects that are in my console.
Here is my function:
$scope.update = function(val) {
angular.forEach($scope.productStuff, function(item){
if( item.s2 === val.toUpperCase()){
$scope.productStuff = [item];
}
});
}
Here is my factory that's getting the data on page load
dataFactory.getProducts().then(function(res){
$scope.productStuff = res.data;
$scope.loading = false;
});
So my question is why is it displaying one object in the dom and multiple objects in the console and how do I put the items on the $scope.productStuff?
$scope.update = function(val) {
// Create an empty array
var stuff = [];
angular.forEach($scope.productStuff, function(item){
if( item.s2 === val.toUpperCase() ){
// push to our array when condition is met (filter)
stuff.push(item);
}
});
// $scope.productStuff now contains all the filtered items
$scope.productStuff = stuff;
}
You are trying to modify iterate over and modifying $scope.productStuff too. As soon as you write:
$scope.productStuff = [item];
only one item remains in it. try creating a new array and once done assign it to $scope.productStuff
$scope.update = function(val) {
var tempArray = [];
angular.forEach($scope.productStuff, function(item){
if( item.s2 === val.toUpperCase()){
tempArray.push(item);
}
});
$scope.productStuff = tempArray;
}

Knockout.js: computed observable not updating as expected

Edit: Added code for function populateDropdown and function isSystemCorrect (see bottom)
Edit 2 I have narrowed it down a bit and the problem seems to arise in the arrayFilter function in the computed observable. This returns an empty array, no matter what I try. I have checked that self.testsuites() looks ok right before filtering, but the filtering still fails.
I have a problem with my computed observable, filteredTestsuites.
As you can see from the screendump, the testsuites observable is populated correctly, but the computed observable remains empty. I have also tried choosing another option than "Payment" from the dropdown menu, to see if this will trigger the observable, it did not.
I would think the computed observable would be updated every time self.testsuites() or self.dropdownSelected() was changed, but it doesnt seem to trigger on neither of them.
What am I doing wrong here?
I simply want to make the computed observable filter the testsuites after the chosen dropdown option, every time either of them change.
function ViewModel() {
var self = this;
// The item currently selected from a dropdown menu
self.dropdownSelected = ko.observable("Payment");
// This will contain all testsuites from all dropdown options
self.testsuites = ko.mapping.fromJS('');
// This will contain only testsuites from the chosen dropdown option
self.filteredTestsuites = ko.computed(function () {
return ko.utils.arrayFilter(self.testsuites(), function (testsuite) {
return (isSystemCorrect(testsuite.System(), self.dropdownSelected()));
});
}, self);
// Function for populating the testsuites observableArray
self.cacheTestsuites = function (data) {
self.testsuites(ko.mapping.fromJS(data));
};
self.populateDropdown = function(testsuiteArray) {
for (var i = 0, len = testsuiteArray().length; i < len; ++i) {
var firstNodeInSystem = testsuiteArray()[i].System().split("/")[0];
var allreadyExists = ko.utils.arrayFirst(self.dropdownOptions(), function(option) {
return (option.Name === firstNodeInSystem);
});
if (!allreadyExists) {
self.dropdownOptions.push({ Name: firstNodeInSystem });
}
}
};
}
$(document).ready(function () {
$.getJSON("/api/TestSuites", function (data) {
vm.cacheTestsuites(data);
vm.populateDropdown(vm.testsuites());
ko.applyBindings(vm);
});
}
Function isSystemCorrect:
function isSystemCorrect(system, partialSystem) {
// Check if partialSystem is contained within system. Must be at beginning of system and go
// on to the end or until a "/" character.
return ((system.indexOf(partialSystem) == 0) && (((system[partialSystem.length] == "/")) || (system[partialSystem.length] == null)));
}
As suggested in a comment - rewrite the cacheTestsuites method:
self.testsuites = ko.observableArray();
self.filteredTestsuites = ko.computed(function () {
return ko.utils.arrayFilter(self.testsuites(), function (testsuite) {
return (isSystemCorrect(testsuite.System(), self.dropdownSelected()));
});
});
self.cacheTestsuites = function (data) {
var a = ko.mapping.fromJS(data);
self.testsuites(a());
};
The only thing different here is the unwrapping of the observableArray from the mapping function.

How to refer Knockout.js objects and array of objects in right context

I canĀ“t seem to get this right: http://jsfiddle.net/dPyQy/4/
I want to collect the tags I am entering in the input field for saving. I need to reference the Textbatches as a SelectedText as in RL I need to be able to choose among Textbatches.
var Textbatch = function (data,tags) {
var self = this;
self.TextbatchId = data.TextbatchId;
self.Title = ko.observable(data.Title);
self.Text = ko.observable(data.Text);
self.TextTags = ko.observableArray(tags);
function createTypeComputed(tagType) {
return ko.computed(function () {
return ko.utils.arrayFilter(self.TextTags(), function (item) {
return item.Type() == tagType;
});
});
}
self.managerTags = createTypeComputed(0);
self.removeTag = function (tagToRemove) {
self.TextTags.remove(function (item) {
return item.Id == tagToRemove.Id;
});
}
}
Struggling with the contexts and the objects and everything. I want the chosen tags listed beneath the input-field, and then the debug to show the updated object.
Any help highly appreciated. Thanx.
managerTags evaluates to an array, but you're using it in a text binding which is expecting it to evaluate to a string.
How do you want to display it? As an HTML list (use a foreach binding to loop over the tags), or as a comma (or something)-separated string (use join on it)?
To get a comma-separated string, change createTypeComputed to
function createTypeComputed(tagType) {
return ko.computed(function () {
return ko.utils.arrayMap(
ko.utils.arrayFilter(self.TextTags(), function (item) {
return item.Type() == tagType;
}),
function (item) {
return item.Name();
}).join(',');
});
}
Note that if you can count on using an ES5 browser (anything but IE<9) you can simplify the function for the computed to
return self.TextTags().filter(function (item) {
return item.Type() == tagType;
})
.map(function (item) {
return item.Name();
})
.join(',');

Knockout is slow when unchecking checkboxes on a large (1000) dataset

I am using this code, to check all checkboxes on my view.
var checked = self.includeAllInSoundscript();
var contents = self.filterContents(self.getFilters());
for (var i = 0; i < contents.length; i++) {
contents[i].includeInSoundscript(checked);
}
return true;
The checkbox
<input type="checkbox" data-bind="checked: includeInSoundscript" title="sometitle" />
This is what contents is:
(function (ko) {
ContentViewModel = function (data) {
this.orderId = data.orderId;
this.contentReferenceId = ko.observable(data.contentReferenceId);
this.includeInSoundscript = ko.observable();
});
This is the filter methods:
self.getFilters = function() {
var filterOrders = $.grep(self.orders(), function (order) {
return (order.usedInfilter());
});
var filterLocations = $.grep(self.locations(), function (location) {
return (location.usedInfilter());
});
return { orders: filterOrders, locations: filterLocations };
};
self.filterContents = function (filter) {
var filteredArray = self.contents();
if (filter.orders.length > 0) {
filteredArray = $.grep(filteredArray, function (content) {
return $.grep(filter.orders, function (order) {
return (order.orderId == content.orderId);
}).length > 0;
});
}
if (filter.locations.length > 0) {
filteredArray = $.grep(filteredArray, function (content) {
return $.grep(filter.locations, function (location) {
return $.inArray(location.location, content.orderedFrom().split('/')) != -1;
}).length > 0;
});
}
return filteredArray;
};
Checking all checkboxes is fast, but when i uncheck, it can take up to 20 seconds. Strange thing is when the filetered result is small, it still takes a bit longer, even if the filtered results is about 40, from a total set of 1000.
The checkbox is in a table, bound using data-bind="foreach: contents"
I have now removed some of the "unescessary" observables, for properties that most likely will not change, it then behaves slightly better, but still very slow, especially in firefox. The big question is, why is this behavior only on unchecking checkboxes, and not on filtering, sorting, checking, etc.
Notice: Its only unchecking the checkboxes, basically when "checked" is false, otherwise its fast.
Edit: I am only displaying 50 items at a time, but i am checking / unchecking all the filtered items. This, so that I have controll over what to post to the server.
This is what I use for this scenario. Maybe it will help you.
The checked binding can work with an array of selected items, but only supports storing strings in the array. I use a custom binding that supports storing objects in the array (like selectedOptions does):
ko.bindingHandlers.checkedInArray = {
init: function (element, valueAccessor) {
ko.utils.registerEventHandler(element, "click", function() {
var options = ko.utils.unwrapObservable(valueAccessor()),
array = options.array, // don't unwrap array because we want to update the observable array itself
value = ko.utils.unwrapObservable(options.value),
checked = element.checked;
ko.utils.addOrRemoveItem(array, value, checked);
});
},
update: function (element, valueAccessor) {
var options = ko.utils.unwrapObservable(valueAccessor()),
array = ko.utils.unwrapObservable(options.array),
value = ko.utils.unwrapObservable(options.value);
element.checked = ko.utils.arrayIndexOf(array, value) >= 0;
}
};
The binding for each checkbox then looks like this:
<input type="checkbox" data-bind="checkedInArray: { array: $parent.selectedItems, value: $data }" />
The checkbox for selecting all items uses the normal checked binding and is attached to a writable computed observable:
this.allItemsSelected = ko.computed({
read: function() {
return this.selectedItems().length === this.items().length;
},
write: function(value) {
this.selectedItems(value ? this.items.slice(0) : [] );
},
owner: this
});
Example: http://jsfiddle.net/mbest/L3LeD/
Update: Knockout 3.0.0 introduced the checkedValue binding option that makes the above custom binding unnecessary. You can now bind the checkboxes like this:
<input type="checkbox" data-bind="checked: $parent.selectedItems, checkedValue: $data" />
Example: http://jsfiddle.net/mbest/RLLX6/
What happens to performance if you use jQuery to check/uncheck all the boxes?
$('#tableId').find('input[type=checkbox]').prop('checked', checked);
Alternatively, could you check all the boxes when you display them, rather than doing all of them in one go?
Also, you could try using the knockout.utils methods for filtering the observable arrays, I'd be interested to see if there's any performance difference there.
var filteredArray = ko.utils.arrayFilter(this.items(), function(item) {
return ko.utils.stringStartsWith(item.name().toLowerCase(), filter);
});
There is also a method for looping over an array and processing each element:
ko.utils.arrayForEach(this.items(), function(item) {
var value = parseFloat(item.priceWithTax());
if (!isNaN(value)) {
total += value;
}
});
Again, I have no idea if this will help with performance or not, though I think it's a bit better prettiness-wise!

Categories

Resources