I have developed some js to enable the filtering of connected fields in the "Add Filter" modal.
I started by creating a new object that holds all the filter "Rules". It looks like this:

The records atm look like this:

In my example below, "Staff" is field_1304 and based on my rules above, I only want to show staff records where field_1308 (which is status) is Active. I get this info from scene_3118 and view_6577.

Here is the js:
////////////////////////////////////////////////////////////////////////////// // This function gets all the filter rules and stores them in a global array. // The scene and view are specified in the url. // The fields are specified to match the fields in the created filter rule object. // Within this function is the only place that code needs to change when moving the code to another Knack App. var getFilterRules = function () { var tryAttempts = 0; // load the filter records var recordUrl = "https://api.knack.com/v1/pages/scene_3119/views/view_6578/records/"; var recordUrl = recordUrl + "?rows_per_page=500";$.ajax( { url: recordUrl, type: "GET", dataType: 'JSON', headers: { 'Authorization': Knack.getUserToken(), 'X-Knack-Application-ID': Knack.application_id, 'X-Knack-REST-API-Key': 'knack' }, tryCount : tryAttempts, retryLimit : 5, success: function (filterData) { //Loop through filterData, build g_fC for (var i = 0; i < filterData.total_records; i++) { g_fC[i] = { //filter Record id: filterData.records[i].id, //Record Id s_field: filterData.records[i].field_3318, //Selected Filter Field t_field: filterData.records[i].field_3319, //Field To Test t_operator: filterData.records[i].field_3320, //Operator to use t_value: filterData.records[i].field_3321, //Value to test against t_scene: filterData.records[i].field_3322, //Ajax Scene t_view: filterData.records[i].field_3323, //Ajax View descriptor: filterData.records[i].field_3324 //Descriptor for Filter List Dropdown } } return g_fC; }, error : function (request, status, error) { tryAttempts = this.tryCount++; if (this.tryCount <= this.retryLimit) { var endTime = new Date(); var startTime = new Date(); do { endTime = new Date(); } while (endTime - startTime < 200); $.ajax(this); } } });
}
// This is where the global variables are declared and the filter rules loaded into an array (once only)
var g_filtersObjectLoaded;if (!g_filtersObjectLoaded) { // Only load this once
g_filtersObjectLoaded = true;var g_fC = []; // Filters Collection var g_fL = []; // Filters List array - used on the Filter Form getFilterRules();
}
// To catch the filter form, need to bind on to something that is always there.
// The filter form is dynamically created so need to start at a higher level than where it appears.
var knackDistEl = document.getElementById(“knack-dist_1”);
$(knackDistEl).on(“click”, “#kn-filters-form”, function (event) {
// isTrigger indicates the “Add Filters” button has been clicked. When this happens, need to reset array
// and reload any previously selected filters
if (event.isTrigger) {
g_fL = ; // Reset Filters List array when filter form displayed
}
var filterFormEl = document.getElementById(“kn-filters-form”);
var filterFieldEls = filterFormEl.getElementsByClassName(“field select”); // This is the left hand side of the filter// Go through each row of filter lines for (var j = 0; j < filterFieldEls.length; j++) { var filterFieldEl = filterFieldEls[j]; var filterFieldOptions = filterFieldEl.getElementsByTagName("option"); var optCount = filterFieldOptions.length; // Find the field selected in each filter row for (var i = 0; i < optCount; i++) { if (filterFieldOptions[i].selected == true) { var selectedField = filterFieldOptions[i]; break; } } // Compare the selected field to the value in our array. // If it is not the same then load it in the array and compare against the filter rules if (selectedField.value != g_fL[j]) { g_fL[j] = selectedField.value; var arrRules = []; var k = 0; var matchRule = []; // Go through all the rules in g_fC and see if the selectedField.value matches any rules // If it does (could be multiple rules) add the rules to the array called arrRules var filterCount = g_fC.length; for (var i = 0; i < filterCount; i++) { if (g_fC[i].s_field == selectedField.value) { matchRule = { 'field': g_fC[i].t_field, 'operator': g_fC[i].t_operator, 'value': g_fC[i].t_value } arrRules[k] = matchRule; k++; var t_scene = g_fC[i].t_scene; var t_view = g_fC[i].t_view; var descriptor = g_fC[i].descriptor; } } // If we have any rules matching, make the Ajax call if (k > 0) { var filterValueEls = filterFormEl.getElementsByClassName("kn-filter-value"); var filterNumber = j; var pFilterValueEl = filterValueEls[filterNumber]; // Protect the Filter Value field until we have reloaded pFilterValueEl.classList.add("protected-fields"); getFilterList(arrRules, t_scene, t_view, descriptor, filterNumber, function (filterList, fNumber) { var incSelected = true; var filterValueEl = filterValueEls[fNumber]; var filterListEls = filterValueEl.querySelectorAll("select"); var filterListEl = filterListEls[0]; // Delay 0.5 seconds as sometimes our build is faster than the initial load var delayInMilliseconds = 500; //0.5 second setTimeout(function () { loadFilterList(filterListEl, filterList, incSelected, function () { filterValueEl.classList.remove("protected-fields"); }); }, delayInMilliseconds); }); } } }
});
var loadFilterList = function (filterListEl, filterList, incSelected, callback) {
var selectVal = []; // Need to start at the end and work backwards when removing options. var L = filterListEl.options.length - 1; for (var i = L; i > 0; i--) { // To keep 'Select' then leave the first option in place. ie >0, not >=0 var selectedOption = filterListEl.options[i].getAttribute("selected"); if (selectedOption != null) { selectVal[i] = filterListEl.options[i].value; } filterListEl.options.remove(i); } // Rebuild the options based on the retrieved data for (var i = 0; i < filterList.length; i++) { // Create an Option object var opt = document.createElement("option"); // Assign text and value to Option object opt.value = (filterList[i][0]); opt.text = (filterList[i][1]); if (incSelected) { for (j = 0; j < selectVal.length; j++) { if (opt.value === selectVal[j]) opt.selected = "selected"; } } // Add Option object to Drop Down List filterListEl.add(opt); } // Update the list $("#" + filterListEl.id).trigger("liszt:updated").change(); $("#" + filterListEl.id).chosen().trigger("change"); callback();
}
var getFilterList = function (arrRules, t_scene, t_view, descriptor, filterNumber, callback) {
var tryAttempts = 0; var filterList = []; // Prepare filters var filters = { 'match': 'and', 'rules': arrRules } var recordUrl = "https://api.knack.com/v1/pages/" + t_scene + "/views/" + t_view + "/records"; var filterUrl = '?rows_per_page=500&filters=' + encodeURIComponent(JSON.stringify(filters)); var totalRecordUrl = recordUrl + filterUrl; $.ajax( { url: totalRecordUrl, type: "GET", dataType: 'JSON', headers: { 'Authorization': Knack.getUserToken(), 'X-Knack-Application-ID': Knack.application_id, 'X-Knack-REST-API-Key': 'knack' }, tryCount : tryAttempts, retryLimit : 5, success: function (data) { for (var i = 0; i < data.total_records; i++) { filterList[i] = [data.records[i].id, data.records[i][descriptor]]; } callback(filterList, filterNumber); }, error : function (request, status, error) { tryAttempts = this.tryCount++; console.log(tryAttempts); if (this.tryCount <= this.retryLimit) { var endTime = new Date(); var startTime = new Date(); do { endTime = new Date(); } while (endTime - startTime < 200); $.ajax(this); } } });
}
// Make sure all the filter selections on all views are not clickable.
$(document).on(‘knack-view-render.any’, function (event, view, data) {
var viewEl = document.getElementById(view.key);
if (viewEl != null) {
var editFilterEls = viewEl.querySelectorAll(".kn-edit-filter");
for (var i = 0; i < editFilterEls.length; i++) {
editFilterEls[i].classList.add(“protected-fields”);
}
}
});
and a little bit of CSS:
/* Protects Fields while loading */ .protected-fields { pointer-events: none; opacity: 0.4; /* Opacity is for test use only */ }
If you have any questions, suggestions or improvements, please let me know.
Tony