Skip to content

Instantly share code, notes, and snippets.

@lucasjellema
Created August 20, 2017 06:42
Show Gist options
  • Save lucasjellema/e133e5e769c13ba8507a3bee0ebc30d1 to your computer and use it in GitHub Desktop.
Save lucasjellema/e133e5e769c13ba8507a3bee0ebc30d1 to your computer and use it in GitHub Desktop.
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