1

I'm trying and failing to filter a list from another array with checkboxes. Essentially I have an array of jobs which inlcude an array of locations in each job. I have checkboxes of the options and have created an array of chosenLocations.

All of the above is working but I can't achieve the filtered list I require. The filtered list is displaying and is exactly the same as hiringManagerJobs, but the filtering isn't working.

I've posted my code below. Issue is in the viewModel.filteredJobs function.

KnockoutJs

var Search = function(){
var self = this;

self.hiringManagerJobs = ko.observableArray();

self.hiringManagerFilterSearchTerm = ko.observable();
self.hiringManagerFilterId = document.getElementById('hiringManagerId').value
self.LocationsFacets = ko.observableArray();
self.chosenLocations = ko.observableArray();
self.filteredJobs = ko.observableArray();


//self.filteredJobs = ko.observableArray();

searchByHiringManager = function () {
    $.get('/...
    }).done(function (data) {
        self.hiringManagerJobs(data.Results);
        self.LocationsFacets(data.Facets.Locations);
    })
}
searchByHiringManager();
}

var viewModel = new Search();


viewModel.filteredJobs = ko.computed(function () {
    var chosenLocations = ko.utils.arrayFilter(viewModel.LocationFacets, 
                       function (p) {
                       return p.selected();
    });
    var jobs = viewModel.hiringManagerJobs();
    if (chosenLocations.length == 0)   //if none selected return all
        return jobs;
    else { //other wise only return selected jobs
        return ko.utils.arrayFilter(jobs, function(job){
        return ko.utils.arrayFilter(job.Locations, function(location){
        return ko.utils.arrayFilter(chosenLocations, 
                                   function(chosenLocation) {
                return location == chosenLocation.Value;

            }).length > 0;
          })
       })

    }
})

ko.applyBindings(viewModel);

HTML

<!-- ko foreach: LocationsFacets -->
    <input type="checkbox" data-bind="checkedValue: $data, checked: $root.chosenLocations" />
    <span data-bind="text: Value"></span>
    <!-- /ko -->

        <ul class="list-group" data-bind="foreach: filteredJobs">

            <li class="list-group-item" data-bind="with: Document">

               <p class="job-title media-heading" data-bind="text: JobTitle"></p>

            </li>

        </ul>

The JSON Arrays in the knockout View Model are like below

"chosenLocations": [
{
  "Type": 0,
  "From": null,
  "To": null,
  "Value": "London",
  "Count": 1
},
{
  "Type": 0,
  "From": null,
  "To": null,
  "Value": "Glasgow",
  "Count": 1
}
],


"hiringManagerJobs": [
    {
  "Score": 1,
  "Highlights": null,
  "Document": {
    "id": "1b41ce24-280d-4fe7-8488-d4babd522bc9",
    "JobTitle": "Test HiringManagerFilterId",
    "CompanyName": "Test Company",
    "ExtUrl": "https://.....",
    "Locations": [
        "London",
        "Manchester",
        "New York"
        ],
    "JobSummary": "blah blah blah",
    "OgLogo": null,
    "HiringManagerFilterId": "xjifu9fdasjkf985ed4"
  }
},
{
  "Score": 1,
  "Highlights": null,
  "Document": {
    "id": "853880b3-fbae-4271-8034-7868c4de63a8",
    "JobTitle": "Senior Manager - Software Development",
    "CompanyName": "Test Company",
    "ExtUrl": "https:......",
    "Locations": [
      "London",
      "Glasgow",
      "Edinburgh"
    ],
    "JobSummary": "blah blah blah ",
    "OgLogo": null,
    "HiringManagerFilterId": "xjifu9fdasjkf985ed4"
  }
}
],

"LocationsFacets": [
{
  "Type": 0,
  "From": null,
  "To": null,
  "Value": "Edinburgh",
  "Count": 1
},
{
  "Type": 0,
  "From": null,
  "To": null,
  "Value": "Glasgow",
  "Count": 1
},
{
  "Type": 0,
  "From": null,
  "To": null,
  "Value": "London",
  "Count": 1
}

],

3
  • I'm trying to reproduce your problem in a fiddle, but it seems some data is missing for data.Facets.Locations. Maybe you could have a look and fill in missing data. Without it, it would be hard to help you out. Commented Aug 11, 2017 at 15:52
  • I have added the location facets in the question Commented Aug 11, 2017 at 16:47
  • Based on the code you've posted it looks like filteredJobs is trying to filter based on p.selected();, but I don't see p.selected being set anywhere so this would always return an empty array, followed by the function returning all jobs "//if none selected return all" Commented Aug 11, 2017 at 18:33

2 Answers 2

1

The main problem is that you're not referring to your data in the way that you've structured it. You've got a local definition of chosenLocations that seems to be left over from a previous iteration of the program. You are looking at job.Locations, but Locations is inside job.Document.

I've used arrayFirst as an analogue of Array.find, which is more like what you want inside the filter, rather than additional filters.

var Search = function() {
  var self = this;

  self.hiringManagerJobs = ko.observableArray();
  self.hiringManagerFilterSearchTerm = ko.observable();
  self.hiringManagerFilterId = 'xjifu9fdasjkf985ed4';
  self.LocationsFacets = ko.observableArray();
  self.chosenLocations = ko.observableArray();
  self.filteredJobs = ko.observableArray();


  //self.filteredJobs = ko.observableArray();

  searchByHiringManager = function() {
    self.hiringManagerJobs([{
        "Score": 1,
        "Highlights": null,
        "Document": {
          "id": "1b41ce24-280d-4fe7-8488-d4babd522bc9",
          "JobTitle": "Test HiringManagerFilterId",
          "CompanyName": "Test Company",
          "ExtUrl": "https://.....",
          "Locations": [
            "London",
            "Manchester",
            "New York"
          ],
          "JobSummary": "blah blah blah",
          "OgLogo": null,
          "HiringManagerFilterId": "xjifu9fdasjkf985ed4"
        }
      },
      {
        "Score": 1,
        "Highlights": null,
        "Document": {
          "id": "853880b3-fbae-4271-8034-7868c4de63a8",
          "JobTitle": "Senior Manager - Software Development",
          "CompanyName": "Test Company",
          "ExtUrl": "https:......",
          "Locations": [
            "London",
            "Glasgow",
            "Edinburgh"
          ],
          "JobSummary": "blah blah blah ",
          "OgLogo": null,
          "HiringManagerFilterId": "xjifu9fdasjkf985ed4"
        }
      }
    ]);
    self.LocationsFacets([{
        "Type": 0,
        "From": null,
        "To": null,
        "Value": "Edinburgh",
        "Count": 1
      },
      {
        "Type": 0,
        "From": null,
        "To": null,
        "Value": "Glasgow",
        "Count": 1
      },
      {
        "Type": 0,
        "From": null,
        "To": null,
        "Value": "London",
        "Count": 1
      }
    ]);
  }
  searchByHiringManager();
}

var viewModel = new Search();


viewModel.filteredJobs = ko.computed(function() {
  const chosenLocations = viewModel.chosenLocations();
  const jobs = viewModel.hiringManagerJobs();

  if (chosenLocations.length == 0) //if none selected return all
    return jobs;
  else { //other wise only return selected jobs
    const result = ko.utils.arrayFilter(jobs, function(job) {
      return ko.utils.arrayFirst(job.Document.Locations, function (location) {
        return ko.utils.arrayFirst(chosenLocations, function (cl) {
          return cl.Value === location;
        });
      });
    });
    return result;
  }
})

ko.applyBindings(viewModel);
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.2/knockout-min.js"></script>
<!-- ko foreach: LocationsFacets -->
<input type="checkbox" data-bind="checkedValue: $data, checked: $root.chosenLocations" />
<span data-bind="text: Value"></span>
<!-- /ko -->

<ul class="list-group" data-bind="foreach: filteredJobs">
  <li class="list-group-item" data-bind="with: Document">
    <p class="job-title media-heading" data-bind="text: JobTitle"></p>
  </li>
</ul>

Sign up to request clarification or add additional context in comments.

1 Comment

Thank you very much Roy. Much appreciated.
0

The self.filteredJobs = ko.observableArray(); is redundant, don't use viewModel.filteredJobs either.

instead directly in your model have the computed.

Note: self.LocationFacets() this is an observable. You had it as viewModel.LocationFacets.

self.filteredJobs = ko.computed(function () {
    var chosenLocations = ko.utils.arrayFilter(self.LocationFacets(), function (p) {
        return p.selected();
    });
    var jobs = self.hiringManagerJobs();
    if (chosenLocations.length == 0) {  
        // if none selected return all
        return jobs;
    }
    else { 
        // other wise only return selected jobs
        return ko.utils.arrayFilter(jobs, function(job) {
            return ko.utils.arrayFilter(job.Locations, function(location){
                return ko.utils.arrayFilter(chosenLocations, function(chosenLocation) {
                    return location == chosenLocation.Value;
                }).length > 0;
             })
        })
    }
})

Note that you state filteredJobs is working, it is just that when you select a location facet it does not trigger the filteredJobs. The selected observable is updating but this would not trigger the filteredJobs to mutate, only a change to LocationFacets or hiringManagerJobs would update this.

If you are expecting the array to mutate on select of facet, you will need this:

 self.selected.subscribe(function (newValue) {
     if (viewModel != null)
          viewModel.LocationFacets.valueHasMutated();
 });

1 Comment

Filtered jobs is working in that foreach: filteredJobs displays all of the jobs. The filtering is not working, that is the problem. The chosen locations are loading in the view model but the list of jobs is NOT filtering accordingly. I believe the issue may be in the logic of the else statement within filteredjobs function.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.