简体   繁体   中英

KnockoutJS: Create an observable from click binding

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.

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

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()));  

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);
    });
});
...

A Functional Approach

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);
    });
});
...

More Information

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!

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM