Features
Real-time filtering of columns of the JavaScript resource calendar component
Filtering by column name
Filtering by column category (people, rooms)
See also the basic JavaScript Resource Calendar Tutorial that explains the setup and configuration of the component.
This project was generated using Resource Calendar UI Builder (online configurator).
License
Licensed for testing and evaluation purposes. Please see the license agreement included in the sample project. You can use the source code of the tutorial if you are a licensed user of DayPilot Pro for JavaScript.
Filter Implementation
The JavaScript resource calendar component filtering API consists of two parts:
DayPilot.Calendar.colums.filter() method applies a filter to the calendar columns
DayPilot.Calendar.onColumnFilter event lets you implement your own filtering rule
The column filtering API is available since build 2018.3.3342.
Filtering Columns by Text
Let's say we want to filter the columns by text in the column name.
To apply a column filter, we need to call columns.filter() method and pass the criteria as a parameter:
dp.columns.filter("room");
This call will invoke onColumnFilter event for each of the columns. We need to add an event handler that will use the supplied criteria to determine whether the column will be visible or not:
dp.onColumnFilter = function(args) {
var criteria = args.filter.toUpperCase();
var columnName = args.column.name.toUpperCase();
if (columnName.indexOf(criteria) === -1) {
args.visible = false;
}
};
The args.filter
property holds the "room" string that we used as an argument when calling columns.filter().
We will normalize the text using toUpperCase()
method:
var criteria = args.filter.toUpperCase();
The args.column
property holds an object with column details. We can access the column name as args.column.name. We will normalize it as well - comparing two normalized strings will make the filter case insensitive:
var columnName = args.column.name.toUpperCase();
If you need case-sensitive search, just use the original string in both cases.
The args.visible
property determines whether the column will be visible (by default it is set to true). We can hide columns that don't meet the rules by setting args.visible to false. In this case, we hide the column if the column name doesn't contain the filter string:
if (columnName.indexOf(criteria) === -1) {
args.visible = false;
}
Clearing the Filter
We can clear the filter by calling columns.filter() without an argument:
dp.columns.filter();
This will display all columns, onColumnFilter
event handler will not be called at all.
Filtering Columns by Category
The same approach can be used to filter columns by category:
dp.columns.filter("People");
In this case, we need to specify a category for each of the columns. One of the options is to add an extra field to the column object (just make sure that the property name doesn't conflict with the built-in properties see DayPilot.Calendar.columns.list):
dp.columns.list = [
{name: "Rooms", children: [
{name: "Room 1", id: "R1", category: "Rooms"},
{name: "Room 2", id: "R2", category: "Rooms"},
{name: "Room 3", id: "R3", category: "Rooms"},
{name: "Room 4", id: "R4", category: "Rooms"},
{name: "Room 5", id: "R5", category: "Rooms"},
{name: "Room 6", id: "R6", category: "Rooms"},
{name: "Room 7", id: "R7", category: "Rooms"},
{name: "Room 8", id: "R8", category: "Rooms"}
]},
{name: "People", children: [
{name: "Martin", id: "P1", category: "People"},
{name: "Lisa", id: "P2", category: "People"},
{name: "Diego", id: "P3", category: "People"},
{name: "Lucy", id: "P4", category: "People"}
]}
];
The filtering event handler needs to compare the selected category with the column data. The original column object is available as args.column.data
- we can access our custom category property as args.column.data.category
:
dp.onColumnFilter = function(args) {
var criteria = args.filter;
var columnCategory = args.column.data.category;
if (criteria != "All" && criteria != columnCategory) {
args.visible = false;
}
};
Complex Filter
Now we can combine both filters. We need to change the columns.filter()
parameter from a simple string to an object that holds both filter condition:
var filter = {
name: "a",
category: "People"
};
dp.columns.filter(filter);
The filter implementation only marks the column as visible if both conditions are met:
dp.onColumnFilter = function(args) {
var name = args.filter.name;
var category = args.filter.category;
var nameMatches = false;
var categoryMatches = false;
// no text specified
if (!name) {
nameMatches = true;
}
// filter text matches the column name, case insensitive
else if (args.column.name.toUpperCase().indexOf(name.toUpperCase()) > -1) {
nameMatches = true;
}
// all categories
if (category === "All") {
categoryMatches = true;
}
// category matches the column category
else if (args.column.data.category === category) {
categoryMatches = true;
}
args.visible = nameMatches && categoryMatches;
};
Highlighting the Filter Text
In the last step, we will highlight the filter text in the column headers. This can be done easily using onBeforeHeaderRender event handler. This event handler is fired for every column header during resource calendar update - we just need to add the highlight to the header HTML.
dp.onBeforeHeaderRender = function(args) {
if (filter.name) {
// case-insensitive search and replace
var escaped = filter.name.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&');
var search = new RegExp("(" + escaped + ")", 'ig');
args.header.html = args.header.name.replace(search, "<span style='background-color: #e69138; color: #fff;'>$1</span>")
}
};
Full Source Code
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8"/>
<title>JavaScript Resource Calendar: Column Filtering</title>
<style type="text/css">
/* ... */
</style>
<!-- DayPilot library -->
<script src="js/daypilot/daypilot-all.min.js"></script>
</head>
<body>
<div class="header">
<h1><a href='https://code.daypilot.org/25430/javascript-resource-calendar-column-filtering'>JavaScript Resource Calendar: Column Filtering</a></h1>
<div><a href="https://javascript.daypilot.org/">DayPilot for JavaScript</a> - HTML5 Calendar/Scheduling Components for JavaScript/Angular/React</div>
</div>
<div class="main">
<div class="filter-section">
Filter columns
by name: <input id="filter" />
category: <select id="category"><option>All</option><option>People</option><option>Rooms</option></select>
<a href="#" id="clear">✖ Clear</a>
</div>
<div id="dp"></div>
</div>
<script>
var dp = new DayPilot.Calendar("dp", {
viewType: "Resources",
headerLevels: 2,
headerHeight: 30,
cellHeight: 30,
hourWidth: 60,
showCurrentTimeMode: "Full",
showCurrentTime: false,
onColumnFilter: function(args) {
var name = args.filter.name;
var category = args.filter.category;
var nameMatches = false;
var categoryMatches = false;
// no text specified
if (!name) {
nameMatches = true;
}
// filter text matches the column name, case insensitive
else if (args.column.name.toUpperCase().indexOf(name.toUpperCase()) > -1) {
nameMatches = true;
}
// all categories
if (category === "All") {
categoryMatches = true;
}
// category matches the column category
else if (args.column.data.category === category) {
categoryMatches = true;
}
args.visible = nameMatches && categoryMatches;
},
onBeforeHeaderRender: function(args) {
if (filter.name) {
// case-insensitive search and replace
var escaped = filter.name.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&');
var search = new RegExp("(" + escaped + ")", 'ig');
args.header.html = args.header.name.replace(search, "<span style='background-color: #e69138; color: #fff;'>$1</span>")
}
},
eventDeleteHandling: "Update",
onTimeRangeSelected: function (args) {
DayPilot.Modal.prompt("Create a new event:", "Event 1").then(function(modal) {
var dp = args.control;
dp.clearSelection();
if (!modal.result) { return; }
dp.events.add(new DayPilot.Event({
start: args.start,
end: args.end,
id: DayPilot.guid(),
text: modal.result,
resource: args.resource
}));
});
}
});
dp.events.list = [
{
id: "1",
start: DayPilot.Date.today().addHours(10),
end: DayPilot.Date.today().addHours(12),
text: "Event 1",
resource: "R1"
}
];
dp.columns.list = [
{name: "Rooms", children: [
{name: "Room 1", id: "R1", category: "Rooms"},
{name: "Room 2", id: "R2", category: "Rooms"},
{name: "Room 3", id: "R3", category: "Rooms"},
{name: "Room 4", id: "R4", category: "Rooms"},
{name: "Room 5", id: "R5", category: "Rooms"},
{name: "Room 6", id: "R6", category: "Rooms"},
{name: "Room 7", id: "R7", category: "Rooms"},
{name: "Room 8", id: "R8", category: "Rooms"}
]},
{name: "People", children: [
{name: "Martin", id: "P1", category: "People"},
{name: "Lisa", id: "P2", category: "People"},
{name: "Diego", id: "P3", category: "People"},
{name: "Lucy", id: "P4", category: "People"}
]}
];
dp.init();
</script>
<script>
var filter = {
name: null,
category: "All"
};
var elements = {
filter: document.querySelector("#filter"),
category: document.querySelector("#category"),
clear: document.querySelector("#clear")
};
elements.filter.addEventListener("keyup", function() {
filter.name = this.value;
dp.columns.filter(filter);
});
elements.category.addEventListener("change", function() {
filter.category = this.value;
dp.columns.filter(filter);
});
elements.clear.addEventListener("click", function(ev) {
ev.preventDefault();
elements.filter.value = "";
elements.category.value = "All";
filter.name = null;
filter.category = "All";
dp.columns.filter(null);
});
</script>
</body>
</html>