Oracle JET - Table Filtering with multiselect filter and search field filter (with Node REST API)
var express = require('express'); | |
var path = require('path'); | |
var favicon = require('serve-favicon'); | |
var logger = require('morgan'); | |
var cookieParser = require('cookie-parser'); | |
var bodyParser = require('body-parser'); | |
var routes = require('./routes/index'); | |
var users = require('./routes/users'); | |
var app = express(); | |
var locations = ['AMSTERDAM','ZOETERMEER','NIEUWEGEIN','MAASTRICHT'] | |
var departments = JSON.parse(require('fs').readFileSync('./departments.json', 'utf8')); | |
// add a location to each record | |
for (i = 0; i < departments.length; i++) { | |
departments[i].location = locations[Math.floor(Math.random() * locations.length)] ; | |
} | |
app.get('/departments', function (req, res) { //process | |
var nameFilter = req.query.name; // read query parameter name (/departments?name=VALUE) | |
// filter departments by the name filter | |
res.send( departments.filter(function (department, index, departments) | |
{ return !nameFilter ||department.DEPARTMENT_NAME.toLowerCase().indexOf(nameFilter)>-1; | |
}) | |
); //using send to stringify and set content-type | |
}); | |
// view engine setup | |
app.set('views', path.join(__dirname, 'views')); | |
app.set('view engine', 'jade'); | |
// uncomment after placing your favicon in /public | |
//app.use(favicon(path.join(__dirname, 'public', 'favicon.ico'))); | |
app.use(logger('dev')); | |
app.use(bodyParser.json()); | |
app.use(bodyParser.urlencoded({ extended: false })); | |
app.use(cookieParser()); | |
app.use(express.static(path.join(__dirname, 'public'))); | |
app.use('/', routes); | |
app.use('/users', users); | |
// catch 404 and forward to error handler | |
app.use(function(req, res, next) { | |
var err = new Error('Not Found'); | |
err.status = 404; | |
next(err); | |
}); | |
// error handlers | |
// development error handler | |
// will print stacktrace | |
if (app.get('env') === 'development') { | |
app.use(function(err, req, res, next) { | |
res.status(err.status || 500); | |
res.render('error', { | |
message: err.message, | |
error: err | |
}); | |
}); | |
} | |
// production error handler | |
// no stacktraces leaked to user | |
app.use(function(err, req, res, next) { | |
res.status(err.status || 500); | |
res.render('error', { | |
message: err.message, | |
error: {} | |
}); | |
}); | |
module.exports = app; |
[ | |
{ | |
"DEPARTMENT_ID": 5, | |
"DEPARTMENT_NAME": "Fontys Hogeschool" | |
}, | |
{ | |
"DEPARTMENT_ID": 10, | |
"DEPARTMENT_NAME": "Administration" | |
}, | |
{ | |
"DEPARTMENT_ID": 20, | |
"DEPARTMENT_NAME": "Marketing" | |
}, | |
{ | |
"DEPARTMENT_ID": 30, | |
"DEPARTMENT_NAME": "Purchasing" | |
}, | |
{ | |
"DEPARTMENT_ID": 40, | |
"DEPARTMENT_NAME": "Human Capital" | |
}, | |
{ | |
"DEPARTMENT_ID": 50, | |
"DEPARTMENT_NAME": "Shipping" | |
}, | |
{ | |
"DEPARTMENT_ID": 60, | |
"DEPARTMENT_NAME": "IT" | |
}, | |
{ | |
"DEPARTMENT_ID": 70, | |
"DEPARTMENT_NAME": "Public Relations" | |
}, | |
{ | |
"DEPARTMENT_ID": 80, | |
"DEPARTMENT_NAME": "Sales" | |
}, | |
{ | |
"DEPARTMENT_ID": 90, | |
"DEPARTMENT_NAME": "Executive" | |
}, | |
{ | |
"DEPARTMENT_ID": 100, | |
"DEPARTMENT_NAME": "Finance" | |
}, | |
{ | |
"DEPARTMENT_ID": 110, | |
"DEPARTMENT_NAME": "Accounting" | |
}, | |
{ | |
"DEPARTMENT_ID": 120, | |
"DEPARTMENT_NAME": "Treasury" | |
}, | |
{ | |
"DEPARTMENT_ID": 130, | |
"DEPARTMENT_NAME": "Corporate Tax" | |
}, | |
{ | |
"DEPARTMENT_ID": 140, | |
"DEPARTMENT_NAME": "Control And Credit" | |
}, | |
{ | |
"DEPARTMENT_ID": 150, | |
"DEPARTMENT_NAME": "Shareholder Services" | |
}, | |
{ | |
"DEPARTMENT_ID": 160, | |
"DEPARTMENT_NAME": "Benefits" | |
}, | |
{ | |
"DEPARTMENT_ID": 170, | |
"DEPARTMENT_NAME": "Manufacturing" | |
}, | |
{ | |
"DEPARTMENT_ID": 180, | |
"DEPARTMENT_NAME": "Construction" | |
}, | |
{ | |
"DEPARTMENT_ID": 190, | |
"DEPARTMENT_NAME": "Contracting" | |
}, | |
{ | |
"DEPARTMENT_ID": 200, | |
"DEPARTMENT_NAME": "Operations" | |
}, | |
{ | |
"DEPARTMENT_ID": 210, | |
"DEPARTMENT_NAME": "IT Support" | |
}, | |
{ | |
"DEPARTMENT_ID": 220, | |
"DEPARTMENT_NAME": "NOC" | |
}, | |
{ | |
"DEPARTMENT_ID": 230, | |
"DEPARTMENT_NAME": "IT Helpdesk" | |
}, | |
{ | |
"DEPARTMENT_ID": 240, | |
"DEPARTMENT_NAME": "Government Sales" | |
}, | |
{ | |
"DEPARTMENT_ID": 250, | |
"DEPARTMENT_NAME": "Retail Sales" | |
}, | |
{ | |
"DEPARTMENT_ID": 260, | |
"DEPARTMENT_NAME": "Recruiting" | |
}, | |
{ | |
"DEPARTMENT_ID": 270, | |
"DEPARTMENT_NAME": "Payroll" | |
} | |
] |
<h1>HRM Content</h1> | |
<div> | |
The Departments Details | |
</div> | |
<br/> | |
<div id="hrm-content" class="demo-apphrm"> | |
<div class="oj-row"> | |
<div> | |
<label for="selectLocation">Locations</label> | |
<select id="selectLocation" data-bind="ojComponent: { component: 'ojSelect' | |
, options: locationOptions, multiple: true | |
, optionChange:optionChangedHandler, | |
rootAttributes: {style:'max-width:20em'}}"> | |
</select> | |
<div class="oj-flex-item oj-sm-8 "> | |
<div class="oj-flex-item" style="max-width: 400px; white-space: nowrap"> | |
<input aria-label="search box" placeholder="search" data-bind="value: nameSearch, valueUpdate: 'afterkeydown', ojComponent: {component: 'ojInputText', rootAttributes:{'style':'max-width:100%;'}}" | |
/> | |
<div id="searchIcon" class="demo-icon-sprite demo-icon-search demo-search-position"></div> | |
<button id="clearButton" data-bind="click: clearClick, | |
ojComponent: { | |
component: 'ojButton', | |
label: 'Clear', | |
display: 'icons', | |
chroming: 'half', | |
icons: {start:'oj-fwk-icon oj-fwk-icon-cross03'}}"> | |
</button> | |
</div> | |
</div> | |
<div id="deptList" class="oj-md-9 oj-col"> | |
<table id="dept-table" summary="Departments List" data-bind=" | |
ojComponent: {component: 'ojTable', | |
data: dataSource, | |
columns: [{headerText: 'Department Id', | |
field: 'DepartmentId'}, | |
{headerText: 'Department Name', | |
field: 'DepartmentName'}, | |
{headerText: 'Location', | |
field: 'Location'}] | |
}"> | |
</table> | |
</div> | |
</div> | |
</div> | |
</div> |
define(['ojs/ojcore', 'knockout', 'utils', 'jquery', 'ojs/ojmodel', 'ojs/ojknockout-model', 'ojs/ojknockout', 'ojs/ojmodel', 'promise', 'ojs/ojtable', 'ojs/ojarraytabledatasource', 'ojs/ojcollectiontabledatasource', | |
'ojs/ojselectcombobox'], | |
function (oj, ko, utils, $) { | |
/** | |
* The view model for the hrm content view template | |
*/ | |
function hrmViewModel() { | |
var self = this; | |
var dataAPI_URL = 'http://127.0.0.1:3000/departments'; | |
// values for the locations shown in the multiselect | |
self.locationOptions = ko.observableArray([]); | |
// bound to search field | |
self.nameSearch = ko.observable(''); | |
// datasource for the table component | |
self.dataSource = ko.observable(); | |
self.fetchDepartments = function () { | |
new self.DeptCollection().fetch({ | |
success: function (collection, response, options) { | |
self.handleDepartmentsFetch(collection); | |
} | |
}); | |
} | |
// this computed function is implicitly subscribed to self.nameSearch; any changes in the search field will trigger this function | |
self.search = ko.computed(function () { | |
var searchString = self.nameSearch(); | |
if (searchString.length > 2) { | |
self.fetchDepartments(); | |
} | |
}) | |
// event handler for reset button (for search field) | |
self.clearClick = function (data, event) { | |
self.nameSearch(''); | |
self.searchDepartments(); | |
return true; | |
} | |
self.optionChangedHandler = function (event, data) { | |
if (data.option == "value") { | |
// REFILTER the data in self.DeptCol into the collection backing the table | |
self.prepareFilteredDepartmentsCollection(self.deppies, getCurrentlySelectedLocations()); | |
} | |
} | |
function getDepartmentsURL(operation, collection, options) { | |
var url = dataAPI_URL + "?name=" + self.nameSearch(); | |
return url; | |
}; | |
self.Department = oj.Model.extend({ | |
urlRoot: dataAPI_URL, | |
parse: function (response) { | |
return { | |
DepartmentId: response['DEPARTMENT_ID'], | |
DepartmentName: response['DEPARTMENT_NAME'], | |
Location: response['location'] | |
}; | |
}, | |
idAttribute: 'DepartmentId' | |
}); | |
self.DeptCollection = oj.Collection.extend({ | |
customURL: getDepartmentsURL, | |
model: new self.Department() | |
}); | |
// whenever departments have been fetched, this function is called to: | |
// -derive the values in the multiselect component for locations | |
// -set the data source that fuels the table component | |
self.handleDepartmentsFetch = function (collection) { | |
var locationData = new Set(); | |
//collect distinct locations and add to locationData array | |
var locations = collection.pluck('Location'); // get all values for Location attribute | |
// distill distinct values | |
var locationData = new Set(locations.filter((elem, index, arr) => arr.indexOf(elem) === index)); | |
// rebuild locationOptions | |
self.locationOptions.removeAll(); | |
var uniqueLocationsArray = []; | |
for (let location of locationData) { | |
uniqueLocationsArray.push({ 'value': location, 'label': location }); | |
} | |
ko.utils.arrayPushAll(self.locationOptions(), uniqueLocationsArray); | |
self.locationOptions.valueHasMutated(); | |
// set the selected locations on the select component based on all distinct locations available | |
$("#selectLocation").ojSelect({ "value": Array.from(locationData) }); | |
// now prepare the filtered departments for the data source | |
self.deppies = collection; | |
self.prepareFilteredDepartmentsCollection(collection, locationData); | |
}; | |
// returns an array of the values of the currently selected options in select component with id selectLocation | |
self.getCurrentlySelectedLocations = function () { | |
return $("#selectLocation").ojSelect("option", "value"); | |
} | |
// prepare (possibly filtered) set of departments and set data source for table | |
self.prepareFilteredDepartmentsCollection = function (collection, selectedLocations) { | |
if (collection) { | |
// prepare filteredDepartmentsCollection | |
var filteredDepartmentsCollection = collection.clone(); | |
var selectedLocationsSet = new Set(selectedLocations); | |
var toFilter = []; | |
// find all models in the collection that do not comply with the selected locations | |
for (var i = 0; i < filteredDepartmentsCollection.size(); i++) { | |
var deptModel = filteredDepartmentsCollection.at(i); | |
if (!selectedLocationsSet.has(deptModel.attributes.Location)) { | |
toFilter.push(deptModel) | |
} | |
} | |
// remove all departments that do not qualify according to the locations that are (not) selected | |
filteredDepartmentsCollection.remove(toFilter); | |
// update data source with fresh data and inform any observers of data source (such as the table component) | |
self.dataSource( | |
new oj.CollectionTableDataSource(filteredDepartmentsCollection)); | |
self.dataSource.valueHasMutated(); | |
}// if (collection) | |
} | |
//load the departments | |
self.fetchDepartments(); | |
}// hrmViewModel | |
return hrmViewModel(); | |
}); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment