Reputation: 385
I use KnockoutJS for a job application website using the getJSON-method in JS.
Unfortunately, I get this structure:
offices
new york
washington
los angeles
I use the filter
-function in JS to filter out some cities' office which are not open yet, which works well.
But now I need to filter out all departments except logistics, because I want to show only logistic jobs for a specific city. I want it to be dynamically, so it will only show logistics even if there are more departments to come.
I can't find a good solution to this. Any ideas?
Edit: Here is the dummy JSON:
Upvotes: 0
Views: 132
Reputation: 23372
Since you're interested in jobs, I'd suggest to make a Job
model that merges the data currently defined only by structure in to one handy object.
To flatten your data, you perform a set of reduce
actions:
const jobData={offices:[{location:"ny",departments:[{name:"Logistics",jobs:[{title:"driver for x"},{title:"driver for y"}]},{name:"Finance",jobs:[{title:"CFO"}]}]},{location:"la",departments:[{name:"Logistics",jobs:[{title:"driver for z"}]},{name:"IT",jobs:[{title:"tech support manager"}]}]}]}
const Job = (title, department, officeLocation) => ({
title,
department,
officeLocation
});
const JobList = ({ offices }) => ({
jobs: offices.reduce(
(allJobs, { location, departments }) => departments.reduce(
(allJobs, { name, jobs }) => jobs.reduce(
(allJobs, { title }) => allJobs.concat(
Job(title, name, location)
),
allJobs
),
allJobs
),
[]
)
})
console.log(JobList(jobData))
Now that we have our data format sorted out, we can start writing the knockout code.
I've created a table
that renders a computed list of jobs. In the computation, we filter on 2 properties: a required office, and a required department.
The filters itself are "flat", because the Job
object has al the data we need. E.g., the Logistics Filter can be applied like:
const logisticsJobs = ko.pureComputed(
jobList().filter(job => job.department === "logistics")
);
Here's the example. Use the <select>
elements in the table header to apply filters.
function JobFinder() {
const jobData = ko.observable({ offices: [] });
const jobList = ko.pureComputed(
() => JobList(jobData())
);
// Lists of properties we can filter on
this.offices = ko.pureComputed(
() => uniques(jobList().map(job => job.officeLocation))
);
this.departments = ko.pureComputed(
() => uniques(jobList().map(job => job.department))
);
// Filter values
this.requiredOffice = ko.observable(null);
this.requiredDepartment = ko.observable(null);
// Actual filter logic
const officeFilter = ko.pureComputed(
() => this.requiredOffice()
? job => job.officeLocation === this.requiredOffice()
: () => true
);
const departmentFilter = ko.pureComputed(
() => this.requiredDepartment()
? job => job.department === this.requiredDepartment()
: () => true
);
const allFilters = ko.pureComputed(
() => [ officeFilter(), departmentFilter() ]
)
const filterFn = ko.pureComputed(
() => job => allFilters().every(f => f(job))
)
// The resulting list
this.filteredJobs = ko.pureComputed(
() => jobList().filter(filterFn())
);
// To load the data (can be async in real app)
this.loadJobData = function() {
jobData(getJobData());
}
};
// Initialize app
const app = new JobFinder();
ko.applyBindings(app);
app.loadJobData();
// utils
function uniques(xs) { return Array.from(new Set(xs)); }
// Code writen in the previous snippet:
function getJobData() {
return {offices:[{location:"ny",departments:[{name:"Logistics",jobs:[{title:"driver for x"},{title:"driver for y"}]},{name:"Finance",jobs:[{title:"CFO"}]}]},{location:"la",departments:[{name:"Logistics",jobs:[{title:"driver for z"}]},{name:"IT",jobs:[{title:"tech support manager"}]}]}]};
};
function Job(title, department, officeLocation) {
return {
title,
department,
officeLocation
}
};
function JobList({ offices }) {
return offices.reduce(
(allJobs, { location, departments }) => departments.reduce(
(allJobs, { name, jobs }) => jobs.reduce(
(allJobs, { title }) => allJobs.concat(
Job(title, name, location)
),
allJobs
),
allJobs
),
[]
)
};
th {
text-align: left;
width: 30%
};
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.2/knockout-min.js"></script>
<table>
<thead>
<tr>
<th>Job Title</th>
<th>Location</th>
<th>Department</th>
</tr>
<tr>
<th></th>
<th>
<select data-bind="
options: offices,
value: requiredOffice,
optionsCaption: 'Show all locations'">
</select>
</th>
<th>
<select data-bind="
options: departments,
value: requiredDepartment,
optionsCaption: 'Show all departments'">
</select>
</th>
</tr>
</thead>
<tbody data-bind="foreach: filteredJobs">
<tr>
<td data-bind="text: title"></td>
<td data-bind="text: officeLocation"></td>
<td data-bind="text: department"></td>
</tr>
</tbody>
</table>
Upvotes: 1