Reputation: 1799
I may be over-thinking this, but what I am trying to do is to create an observableArray from a click event bound to a function. Basically, when I click on a button, I need to filter results based on event.target.html()
and create an observable that I can use in a foreach
loop later in the page.
First, I have a button.
<button data-bind="click: getData">Button Text</button>
Function that filters results based on Button Text
self.filterFunction = function(x) {
// do things here
return (obj);
}
Click is bound to the getData
function, which calls filterFunction
with DOM element from the click
and creates an observable array
self.getData = function(item, event) {
viewModel.myFilteredData = ko.observableArray(self.filterFunction($(event.target).html()));
}
And then I loop through myFilteredData
, but fail miserably.
<!-- ko foreach: myFilteredData -->
// Generate HTML here
<!-- /ko -->
Something tells me that I fail at logic and there is a more efficient way of doing this. I also would like to figure out a way to have myFilteredData
pre-populated based on some pre-set Button Text
, so the HTML can be generated on initial page load, before any buttons are clicked.
Thank you.
Upvotes: 0
Views: 1233
Reputation: 1802
Solefald, there's definitely a better way to do this.
First, add an array of your own "filter" objects to your view model, as well as a ko.computed to provide your filtered data. Bind your filter buttons to the array of "filter" objects. Something like this:
var viewModel = function(){
var self = this;
self.filters = [
{buttonText:'Button Text', filterKey:'filterKeyOne'},
{buttonText:'Other Button Text', filterKey:'filterKeyTwo'}
];
self.selectedFilterKey = ko.observable('none');
self.setFilter = function(){...}
self.data = ko.observableArray([...]);
self.filteredData = ko.computed(function(){...});
}
and bind like this:
<div data-bind="foreach: filters">
<button data-bind="click : $parent.setFilter, text: buttonText"></button>
</div>
<div data-bind="foreach: filteredData">
...
</div>
By letting Knockout do the click binding for the filter buttons, you'll get your whole filter object as the first parameter in the "runFilter" function. You can then implement your filter button click handler ("setFilter" in this example) like this:
self.setFilter = function(filterItem){
self.selectedFilterKey(filterItem.filterKey);
};
By making "selectedFilterKey" an observable, you can implement your filteredData as a ko.computed which will automatically update the ui when "selectedFilterKey" changes, like this :
...
self.filteredData = ko.computed(function(){
//pick the right filter function
var filterFunction = filterOne;//assign the default filter function
switch(self.selectedFilterKey()){
case 'filterKeyOne':
filterFunction = self.filterOne;//your first filter
break;
case 'filterKeyTwo':
filterFunction = self.filterTwo;//your second filter
break;
}
return ko.utils.arrayFilter(self.data(), function(item){
//return true if item passes filter
return filterFunction(item);
});
});
...
If you'd like to use a more functional programming approach, try defining your filter functions right in the filter objects.
self.filters = [
{buttonText:'Button Text', filterFunction:function(item){...}},
{buttonText:'Other Button Text', filterFunction:function(item){...}}
];
and store the selected function, rather than a key
self.setFilter = function(filterItem){
self.selectedFilterFunction(filterItem.filterFunction);
};
and skip the switch statement
...
self.filteredData = ko.computed(function(){
return ko.utils.arrayFilter(self.data(), function(item){
//return true if item passes filter
return self.selectedFilterFunction()(item);
});
});
...
If you'd like more info about this approach for sorting, you can read about it on my blog here : http://ryanrahlf.com/sorting-tables-by-column-header-with-knockout-js/
I hope this helps!
Upvotes: 2
Reputation: 78850
Perhaps the problem you're having is that your foreach
binding is bound to a different observable array than the one you generate after the click. Think of it this way. Given this view model:
{
myFilteredData: ko.observableArray()
}
...when the ko.applyBindings
happens, Knockout subscribes to this ko.observableArray
for updates, and loops through the (now empty) array.
Now, your click handler occurs:
viewModel.myFilteredData = ko.observableArray(self.filterFunction($(event.target).html()));
The subscription established above isn't triggered because the observable array didn't change; instead, you replaced it with another one. Unfortunately, Knockout doesn't know about the new one.
I'd recommend that instead of replacing the array with a new one, you populate the existing array with the new data:
viewModel.myFilteredData(self.filterFunction($(event.target).html()));
Upvotes: 2
Reputation: 14995
Why don't you just set an observableArray in your view model, create a function bound to your button click, and pass in each object? Bind the buttons with a for each to some observables or entities, and then on button click have a parameter that you push into the observableArray
Upvotes: 2