define([
"jquery",
"underscore",
"backbone",
"collections/Filters",
"models/filters/Filter",
"models/filters/FilterGroup",
"views/filters/FilterGroupView",
"views/filters/FilterView",
"text!templates/filters/filterGroups.html",
], function (
$,
_,
Backbone,
Filters,
Filter,
FilterGroup,
FilterGroupView,
FilterView,
Template,
) {
"use strict";
/**
* @class FilterGroupsView
* @classdesc Creates a view of one or more FilterGroupViews
* @classcategory Views/Filters
* @name FilterGroupsView
* @extends Backbone.View
* @constructor
*/
var FilterGroupsView = Backbone.View.extend(
/** @lends FilterGroupsView.prototype */ {
/**
* The FilterGroup models to display in this view
* @type {FilterGroup[]}
*/
filterGroups: [],
/**
* The Filters Collection that contains the same Filter
* models from each FilterGroup and any additional Filter Models that may not be in
* FilterGroups because they're not displayed or applied behind the scenes.
* @type {Filters}
*/
filters: null,
/**
* A reference to the PortalEditorView
* @type {PortalEditorView}
*/
editorView: undefined,
/**
* @inheritdoc
*/
tagName: "div",
/**
* @inheritdoc
*/
className: "filter-groups tabbable",
/**
* The template for this view. An HTML file is converted to an Underscore.js template
* @since 2.17.0
*/
template: _.template(Template),
/**
* If true, displays the FilterGroups in a vertical list
* @type {Boolean}
*/
vertical: false,
/**
* Set to true to render this view as a FilterGroups editor; allow the user add, edit,
* and remove FilterGroups (TODO), and to add, delete, and edit filters within groups.
* @type {boolean}
* @since 2.17.0
*/
edit: false,
/**
* If set to true, then all filters within this group will be collapsible.
* See {@link FilterView#collapsible}
* @type {boolean}
* @since 2.25.0
* @default false
*/
collapsible: false,
/**
* The initial query to use when the view is first rendered. This is a text value
* that will be set on the general `text` Solr field.
* @type {string}
* @since 2.25.0
*/
initialQuery: undefined,
/**
* @inheritdoc
*/
events: {
"click .remove-filter": "handleRemove",
"click .clear-all": "removeAllFilters",
},
/**
* @inheritdoc
*/
initialize: function (options) {
if (!options || typeof options != "object") {
var options = {};
}
this.filterGroups = options.filterGroups || new Array();
this.filters = options.filters || new Filters();
// For portal search filters, ID filters should be added to the query with an AND
// operator, so that ID searches search *within* the definition collection.
if (this.filters) {
this.filters.mustMatchIds = true;
}
if (options.vertical == true) {
this.vertical = true;
}
this.parentView = options.parentView || null;
this.editorView = options.editorView || null;
if (options.edit === true) {
this.edit = true;
}
if (options.initialQuery) {
this.initialQuery = options.initialQuery;
}
if (options.collapsible && typeof options.collapsible === "boolean") {
this.collapsible = options.collapsible;
}
},
/**
* @inheritdoc
*/
render: function () {
//Since this view may be re-rendered at some point, empty the element and remove listeners
this.$el.empty();
this.stopListening();
// Add information about editing the filter groups if this view is in edit mode
if (this.edit) {
var title =
"Change how people can search for data within your collection";
var isNew = this.filterGroups.length === 0;
if (
this.filterGroups.length === 1 &&
this.filterGroups[0].isEmpty()
) {
var isNew = true;
}
if (isNew) {
title =
"Add filters to help people find data within your collection";
}
var description =
"Search filters allow people to filter your data by specific " +
"metadata fields.",
learnMoreUrl = MetacatUI.appModel.get("portalSearchFiltersInfoURL");
if (learnMoreUrl) {
description =
description + ' <a href="' + learnMoreUrl + '">Learn more</a>';
}
this.$el.html(
this.template({
title: title,
description: description,
helpText: "",
}),
);
// Remove this when the custom search filter builder is no longer new:
this.$el
.find(".port-editor-subtitle")
.append(
$(
'<span class="new-icon" style="margin-left:10px; font-size:1rem; line-height: 25px;"><i class="icon icon-star icon-on-right"></i> NEW </span>',
),
);
}
//Create an unordered list for all the filter tabs
var groupTabs = $(document.createElement("ul")).addClass(
"nav nav-tabs filter-group-links",
);
// Until we allow adding/editing filter groups in the portal data page, hide the group tabs
// element if the portal does not already have groups in the editor.
if (
this.filterGroups.length === 1 &&
!this.filterGroups[0].get("label") &&
!this.filterGroups[0].get("icon")
) {
groupTabs.hide();
}
//Create a container div for the filter groups
var filterGroupContainer = $(document.createElement("div")).addClass(
"tab-content",
);
//Add the filter group elements to this view
this.$el.append(groupTabs, filterGroupContainer);
var divideIntoGroups = true;
_.each(
this.filterGroups,
function (filterGroup) {
//If there is only one filter group specified, and there is no label or icon,
// then don't divide the filters into separate filter groups
if (
this.filterGroups.length == 1 &&
!this.filterGroups[0].get("label") &&
!this.filterGroups[0].get("icon")
) {
divideIntoGroups = false;
}
if (divideIntoGroups) {
//Create a link to the filter group
var groupTab = $(document.createElement("li")).addClass(
"filter-group-link",
);
var groupLink = $(document.createElement("a"))
.attr(
"href",
"#" + filterGroup.get("label").replace(/([^a-zA-Z0-9])/g, ""),
)
.attr("data-toggle", "tab");
//Add the FilterGroup icon
if (filterGroup.get("icon")) {
groupLink.append(
$(document.createElement("i")).addClass(
"icon icon-" + filterGroup.get("icon"),
),
);
}
//Add the FilterGroup label
if (filterGroup.get("label")) {
groupLink.append(filterGroup.get("label"));
}
//Insert the link into the tab and add the tab to the tab list
groupTab.append(groupLink);
groupTabs.append(groupTab);
//Create a tooltip for the link
groupTab.tooltip({
placement: "top",
title: filterGroup.get("description"),
trigger: "hover",
delay: {
show: 800,
},
});
//Make all the tab widths equal
groupTab.css("width", 100 / this.filterGroups.length + "%");
}
// Create a FilterGroupView. Ensure the FilterGroup is in edit mode if the parent
// FilterGroups is.
var filterGroupView = new FilterGroupView({
model: filterGroup,
edit: this.edit,
editorView: this.editorView,
collapsible: this.collapsible,
});
//Render the FilterGroupView
filterGroupView.render();
//Add the FilterGroupView element to this view
filterGroupContainer.append(filterGroupView.el);
//Store a reference to the FilterGroupView in the tab link
if (divideIntoGroups) {
groupLink.data("view", filterGroupView);
}
//If a new filter is ever added to this filter group, re-render this view
this.listenTo(
filterGroup.get("filters"),
"add remove",
this.render,
);
},
this,
);
if (divideIntoGroups) {
//Mark the first filter group as active
groupTabs.children("li").first().addClass("active");
//When each filter group tab is shown, perform any post render function, if needed.
this.$('a[data-toggle="tab"]').on("shown", function (e) {
//Get the filter group view
var filterGroupView = $(e.target).data("view");
//If there is a post render function, call it
if (filterGroupView && filterGroupView.postRender) {
filterGroupView.postRender();
}
});
}
//Mark the first filter group as active
var firstFilterGroupEl = filterGroupContainer
.find(".filter-group")
.first();
firstFilterGroupEl.addClass("active");
var activeFilterGroup = firstFilterGroupEl.data("view");
//Call postRender() now for the active FilterGroup, since the `shown` event
// won't trigger until/unless it's hidden then shown again.
if (activeFilterGroup) {
activeFilterGroup.postRender();
}
// Applied filters and the general search input are not needed when this view is
// in editing mode
if (!this.edit) {
//Add a header element above the filter groups
this.$el.prepend(
$(document.createElement("div")).addClass("filters-header"),
);
//Render the applied filters
this.renderAppliedFiltersSection();
// Render an "All" filter. If the view was initialized with an initial
// query, set it on this filter.
this.renderAllFilter(this.initialQuery);
}
if (this.edit) {
this.$el.addClass("edit-mode");
}
if (this.vertical) {
this.$el.addClass("vertical");
}
},
/**
* Renders the section of the view that will display the currently-applied filters
*/
renderAppliedFiltersSection: function () {
//Add a title to the header
var appliedFiltersContainer = $(document.createElement("div")).addClass(
"applied-filters-container",
),
headerText = $(document.createElement("h5"))
.addClass("filters-title")
.text("Current search")
.append(
$(document.createElement("a"))
.text("Clear all")
.addClass("clear-all")
.prepend(
$(document.createElement("i")).addClass(
"icon icon-remove icon-on-left",
),
),
);
//Make the applied filters list
var appliedFiltersEl = $(document.createElement("ul")).addClass(
"applied-filters",
);
//Add the applied filters element to the filters header
appliedFiltersContainer.append(headerText, appliedFiltersEl);
this.$(".filters-header").append(appliedFiltersContainer);
//Get all the nonNumeric filter models. Reject nested filterGroups.
var nonNumericFilters = this.filters.reject(function (filterModel) {
return ["FilterGroup", "NumericFilter", "DateFilter"].includes(
filterModel.type,
);
});
//Listen to changes on the "values" attribute for nonNumeric filters
_.each(
nonNumericFilters,
function (nonNumericFilter) {
this.listenTo(
nonNumericFilter,
"change:values",
this.updateAppliedFilters,
);
if (nonNumericFilter.get("values").length) {
this.updateAppliedFilters(nonNumericFilter, {
displayWithoutChanges: true,
});
}
},
this,
);
//Get the numeric filters and listen to the min and max values
var numericFilters = _.where(this.filters.models, {
type: "NumericFilter",
});
_.each(
numericFilters,
function (numericFilter) {
if (numericFilter.get("range") == true) {
this.listenTo(
numericFilter,
"change:min change:max",
this.updateAppliedRangeFilters,
);
var filterDefaults = numericFilter.defaults();
if (
numericFilter.get("min") != filterDefaults.min ||
numericFilter.get("max") != filterDefaults.max ||
numericFilter.get("values").length
) {
this.updateAppliedRangeFilters(numericFilter, {
displayWithoutChanges: true,
});
}
} else {
this.listenTo(
numericFilter,
"change:values",
this.updateAppliedRangeFilters,
);
if (
numericFilter.get("values")[0] !=
numericFilter.defaults().values[0]
) {
this.updateAppliedRangeFilters(numericFilter, {
displayWithoutChanges: true,
});
}
}
},
this,
);
//Get the date filters and listen to the min and max values
var dateFilters = _.where(this.filters.models, { type: "DateFilter" });
_.each(
dateFilters,
function (dateFilter) {
this.listenTo(
dateFilter,
"change:min change:max",
this.updateAppliedRangeFilters,
);
if (
dateFilter.get("min") != dateFilter.defaults().min ||
dateFilter.get("max") != dateFilter.defaults().max
) {
this.updateAppliedRangeFilters(dateFilter, {
displayWithoutChanges: true,
});
}
},
this,
);
//When a Filter has been removed from the Filters collection, remove it's DOM element from the page
this.listenTo(this.filters, "remove", function (removedFilter) {
this.removeAppliedFilterElByModel(removedFilter);
});
},
/**
* Renders an "All" filter that will search the general `text` Solr field
* @param {string} searchFor - The initial value of the "All" filter. This
* will get set on the filter model and trigger a change event. Optional.
*/
renderAllFilter: function (searchFor = "") {
//Create an "All" filter that will search the general `text` Solr field
var filter = new Filter({
fields: ["text"],
label: "Search",
description:
"Search the datasets by typing in any keyword, topic, creator, etc.",
placeholder: "Search these datasets",
});
this.filters.add(filter);
//Create a FilterView for the All filter
var filterView = new FilterView({
model: filter,
});
this.listenTo(filter, "change:values", this.updateAppliedFilters);
//Render the view and add the element to the filters header
filterView.render();
this.$(".filters-header").prepend(filterView.el);
if (searchFor && searchFor.length) {
filter.set("values", [searchFor]);
}
},
postRender: function () {
var groupTabs = this.$(".filter-group-links");
//Check if there is a difference in heights
var maxHeight = 0;
_.each(groupTabs.find("a"), function (link) {
if ($(link).height() > maxHeight) {
maxHeight = $(link).height();
}
});
//Set the height of each filter group link so they are all equal
_.each(groupTabs.find("a"), function (link) {
if ($(link).height() < maxHeight) {
$(link).height(maxHeight + "px");
}
});
},
/**
* Renders the values of the given Filter Model in the current filter model
*
* @param {Filter} filterModel - The FilterModel to display
* @param {object} options - Additional options for this function
* @property {boolean} options.displayWithoutChanges - If true, this filter will
* display even if the value hasn't been changed
*/
updateAppliedFilters: function (filterModel, options) {
//Create an options object if one wasn't sent
if (typeof options != "object") {
var options = {};
}
this.options = options;
var view = this;
//If the value of this filter has changed, or if the displayWithoutChanges option
// was passed, and if the filter is not invisible, then display it
if (
!filterModel.get("isInvisible") &&
((filterModel.changed && filterModel.changed.values) ||
options.displayWithoutChanges)
) {
//Get the new values and the previous values
var newValues = options.displayWithoutChanges
? filterModel.get("values")
: filterModel.changed.values,
previousValues = options.displayWithoutChanges
? []
: filterModel.previousAttributes().values,
//Find the values that were removed
removedValues = _.difference(previousValues, newValues),
//Find the values that were added
addedValues = _.difference(newValues, previousValues);
//If a filter has been added, display it
_.each(
addedValues,
function (value) {
//Add the applied filter to the view
this.$(".applied-filters").append(
this.createAppliedFilter(filterModel, value),
);
},
this,
);
//Iterate over each removed filter value and remove them
_.each(
removedValues,
function (value) {
//Find all applied filter elements with a matching value
var matchingFilters = this.$(
".applied-filter[data-value='" + value + "']",
);
//Iterate over each filter element with a matching value
_.each(matchingFilters, function (matchingFilter) {
//If this is the filter element associated with this filter model, then remove it
if ($(matchingFilter).data("model") == filterModel) {
$(matchingFilter).remove();
}
});
},
this,
);
}
//Toggle the applied filters header
this.toggleAppliedFiltersHeader();
},
/**
* Hides or shows the applied filter list title/header, as well as the help
* message that lets the user know they can add filters when there are none
*/
toggleAppliedFiltersHeader: function () {
//If there is an applied filter
if (this.$(".applied-filter").length) {
// hide the "add some filters" help text
//$(this.parentView.helpTextContainer).css("display", "none");
// show the Clear All button
this.$(".filters-title").css("display", "block");
}
//If there are no applied filters
else {
// show the "add some filters" help text
// $(this.parentView.helpTextContainer).css("display", "block");
// hide the Clear All button
this.$(".filters-title").css("display", "none");
}
},
/**
* When a NumericFilter or DateFilter model is changed, update the applied filters in the UI
* @param {DateFilter|NumericFilter} filterModel - The model whose values to display
* @param {object} [options] - Additional options for this function
* @property {boolean} [options.displayWithoutChanges] - If true, this filter will display even if the value hasn't been changed
*/
updateAppliedRangeFilters: function (filterModel, options) {
if (!filterModel) {
return;
}
if (typeof options === "undefined" || !options) {
var options = {};
}
//If the Filter is invisible, don't render it
if (filterModel.get("isInvisible")) {
return;
}
//If the minimum and maximum values are set to the default, remove the filter element
if (
filterModel.get("min") == filterModel.get("rangeMin") &&
filterModel.get("max") == filterModel.get("rangeMax")
) {
//Find the applied filter element for this filter model
_.each(
this.$(".applied-filter"),
function (filterEl) {
if ($(filterEl).data("model") == filterModel) {
//Remove the applied filter element
$(filterEl).remove();
}
},
this,
);
}
//If the values attribue has changed, or if the displayWithoutChanges attribute was passed
else if (
(filterModel.changed &&
(filterModel.changed.min || filterModel.changed.max)) ||
options.displayWithoutChanges
) {
//Create the filter label for ranges of numbers
var filterValue = filterModel.getReadableValue();
//Create the applied filter
var appliedFilter = this.createAppliedFilter(
filterModel,
filterValue,
);
//Keep track if this filter is already displayed and needs to be replaced
var replaced = false;
//Check if this filter model already has an applied filter in the UI
_.each(
this.$(".applied-filter"),
function (appliedFilterEl) {
//If this applied filter already is displayed, replace it
if ($(appliedFilterEl).data("model") == filterModel) {
//Replace the applied filter element with the new one
$(appliedFilterEl).replaceWith(appliedFilter);
replaced = true;
}
},
this,
);
if (!replaced) {
//Add the applied filter to the view
this.$(".applied-filters").append(appliedFilter);
}
}
this.toggleAppliedFiltersHeader();
},
/**
* Creates a single applied filter element and returns it. Filters can
* have multiple values, so one value is passed to this function at a time.
* @param {Filter} filterModel - The Filter model that is being added to the display
* @param {string|number|Boolean} value - The new value set on the Filter model that is displayed in this applied filter
* @returns {jQuery} - The complete applied filter element
*/
createAppliedFilter: function (filterModel, value) {
//Create the filter label
var filterLabel = filterModel.get("label"),
filterValue = value;
//If the filter type is Choice, get the choice label which can be different from the value
if (filterModel.type == "ChoiceFilter") {
//Find the choice object with the given value
var matchingChoice = _.findWhere(filterModel.get("choices"), {
value: value,
});
//Get the label for that choice
if (matchingChoice) {
filterValue = matchingChoice.label;
}
}
//Create the filter label for boolean filters
else if (filterModel.type == "BooleanFilter") {
//If the filter is set to false, remove the applied filter element
if (filterModel.get("values")[0] === false) {
//Iterate over the applied filters
_.each(
this.$(".applied-filter"),
function (appliedFilterEl) {
//If this is the applied filter element for this model,
if ($(appliedFilterEl).data("model") == filterModel) {
//Remove the applied filter element from the page
$(appliedFilterEl).remove();
}
},
this,
);
//Exit the function at this point since there is nothing else to
// do for false BooleanFilters
return;
} else if (filterModel.get("values")[0] === true) {
if (!filterLabel) {
filterLabel = filterModel.get("fields")[0];
filterValue = "";
}
}
} else if (filterModel.type == "ToggleFilter") {
if (filterModel.get("values")[0] == filterModel.get("trueValue")) {
if (filterModel.get("label") && filterModel.get("trueLabel")) {
filterValue = filterModel.get("trueLabel");
} else if (
!filterModel.get("label") &&
filterModel.get("trueLabel")
) {
filterLabel = "";
filterValue = filterModel.get("trueLabel");
} else if (filterModel.get("label")) {
filterLabel = "";
filterValue = filterModel.get("label");
}
} else {
if (filterModel.get("label") && filterModel.get("falseLabel")) {
filterValue = filterModel.get("falseLabel");
} else if (
!filterModel.get("label") &&
filterModel.get("falseLabel")
) {
filterLabel = "";
filterValue = filterModel.get("falseLabel");
} else if (filterModel.get("label")) {
filterLabel = "";
filterValue = filterModel.get("label");
}
}
}
//If this Filter model is a full-text search, don't display a label
else if (
filterModel.get("fields").length == 1 &&
filterModel.get("fields")[0] == "text"
) {
filterLabel = "";
}
//isPartOf filters should just display the label, not the value
else if (
filterModel.get("fields").length == 1 &&
filterModel.get("fields")[0] == "isPartOf"
) {
filterValue = "";
}
//If the filter value is just an asterisk (i.e. `match anything`), just display the label
else if (
filterModel.get("values").length == 1 &&
filterModel.get("values")[0] == "*"
) {
filterValue = "";
}
//Filters with the valueLabels attribute want to display an alternate value from the raw value here
else if (filterModel.get("valueLabels")) {
filterValue = filterModel.get("valueLabels")[value] || value;
} else if (!filterLabel) {
filterLabel = filterModel.get("fields")[0];
}
//Create the applied filter element
var removeIcon = $(document.createElement("a"))
.addClass("icon icon-remove remove-filter icon-on-right")
.attr("title", "Remove this filter"),
appliedFilter = $(document.createElement("li"))
.addClass("applied-filter label")
.append(removeIcon)
.data("model", filterModel)
.attr("data-value", value);
//Create an element to contain both the label and value
var filterLabelEl = $(document.createElement("span")).addClass("label");
var filterValueEl = $(document.createElement("span"))
.addClass("value")
.text(filterValue);
var filterTextContainer = $(document.createElement("span")).append(
filterLabelEl,
filterValueEl,
);
//If there is both a label and value, separated them with a colon
if (filterLabel && filterValue) {
filterLabelEl.text(filterLabel + ": ");
}
//Otherwise just use the label text only
else if (filterLabel) {
filterLabelEl.text(filterLabel);
}
//Add the filter text to the filter element
appliedFilter.prepend(filterTextContainer);
// Add a tooltip to the filter
if (filterModel.get("description")) {
appliedFilter.tooltip({
placement: "right",
title: filterModel.get("description"),
trigger: "hover",
delay: {
show: 700,
},
});
}
return appliedFilter;
},
/**
* Adds a custom filter that likely exists outside of the FilterGroups but needs
* to be displayed with these other applied fitlers.
*
* @param {Filter} filterModel - The Filter Model to display
*/
addCustomAppliedFilter: function (filterModel) {
//If the Filter is invisible, don't render it
if (filterModel.get("isInvisible")) {
return;
}
//If this filter already exists in the applied filter list, exit this function
var alreadyExists = _.find(
this.$(".applied-filter.custom"),
function (appliedFilterEl) {
return $(appliedFilterEl).data("model") == filterModel;
},
);
if (alreadyExists) {
return;
}
//Create the applied filter element
var removeIcon = $(document.createElement("a"))
.addClass("icon icon-remove remove-filter icon-on-right")
.attr("title", "Remove this filter"),
filterText = $(document.createElement("span")).text(
filterModel.get("label"),
),
appliedFilter = $(document.createElement("li"))
.addClass("applied-filter label custom")
.append(filterText, removeIcon)
.data("model", filterModel)
.attr("data-value", filterModel.get("values"));
if (filterModel.type == "SpatialFilter") {
filterText.prepend(
$(document.createElement("i")).addClass(
"icon icon-on-left icon-" + filterModel.get("icon"),
),
);
}
//Add the applied filter to the view
this.$(".applied-filters").append(appliedFilter);
//Display the filters title
this.toggleAppliedFiltersHeader();
},
/**
* Removes the custom applied filter from the UI.
*
* @param {Filter} filterModel - The Filter Model to display
*/
removeCustomAppliedFilter: function (filterModel) {
_.each(
this.$(".custom.applied-filter"),
function (appliedFilterEl) {
if ($(appliedFilterEl).data("model") == filterModel) {
$(appliedFilterEl).remove();
this.trigger("customAppliedFilterRemoved", filterModel);
}
},
this,
);
//Hide the filters title
this.toggleAppliedFiltersHeader();
},
/**
* When a remove button is clicked, get the filter model associated with it
/* and remove the filter from the filter group
*
* @param {Event} - The DOM Event that occured on the filter remove icon
*/
handleRemove: function (e) {
// Ensure tooltips are removed
try {
if (e.delegateTarget) {
$(e.delegateTarget).find(".tooltip").remove();
}
} catch (e) {
console.log(
"Could not remove tooltip from filter label, error message: " + e,
);
}
//Get the applied filter element and the filter model associated with it
var appliedFilterEl = $(e.target).parents(".applied-filter"),
filterModel = appliedFilterEl.data("model");
if (appliedFilterEl.is(".custom")) {
this.removeCustomAppliedFilter(filterModel);
} else {
//Remove the filter from the filter group model
this.removeFilter(filterModel, appliedFilterEl);
}
},
/**
* Remove the filter from the UI and the Search collection
* @param {Filter} filterModel The Filter to remove from the Filters collection
* @param {Element} appliedFilterEl The DOM Element for the applied filter on the page
* @param {object} options Additional options for this function
* @param {boolean} options.removeSilently If true, the Filter model will be removed siltently from the Filters collection.
* This is useful when removing multiple Filters at once, and triggering a remove/change/reset event after all have
* been removed.
*/
removeFilter: function (filterModel, appliedFilterEl, options) {
var removeSilently = false;
//Create an options object if one wasn't sent
if (typeof options != "object") {
var options = {};
}
this.options = options;
var view = this;
//Parse all the additional options for this function
if (typeof options == "object") {
removeSilently =
typeof options.removeSilently != "undefined"
? options.removeSilently
: false;
}
if (filterModel) {
//NumericFilters and DateFilters get the min and max values reset
if (
filterModel.type == "NumericFilter" ||
filterModel.type == "DateFilter"
) {
//Set the min and max values
filterModel.set({
min: filterModel.get("rangeMin"),
max: filterModel.get("rangeMax"),
values: filterModel.defaults().values,
});
if (!removeSilently) {
//Trigger the reset event
filterModel.trigger("rangeReset");
}
}
//For all other filter types
else {
//Get the current value
var modelValues = filterModel.get("values"),
thisValue = $(appliedFilterEl).data("value");
//Numbers that are set on the element `data` are stored as type `number`, but when `number`s are
// set on Backbone models, they are converted to `string`s. So we need to check for this use case.
if (typeof thisValue == "number") {
//Convert the number to a string
thisValue = thisValue.toString();
}
//Remove the value that was in this applied filter
var newValues = _.without(modelValues, thisValue),
setOptions = {};
if (removeSilently) {
setOptions.silent = true;
}
//Updates the values on the model
filterModel.set("values", newValues, setOptions);
}
}
},
/**
* Gets all the applied filters in this view and their associated filter models
* and removes them.
*/
removeAllFilters: function () {
let removedFilters = [];
//Iterate over each applied filter in the view
_.each(
this.$(".applied-filter"),
function (appliedFilterEl) {
var $appliedFilterEl = $(appliedFilterEl);
removedFilters.push($appliedFilterEl.data("model"));
if ($appliedFilterEl.is(".custom")) {
this.removeCustomAppliedFilter($appliedFilterEl.data("model"));
} else {
//Remove the filter from the fitler group. Do this silently since we will trigger a "reset" event later
this.removeFilter(
$appliedFilterEl.data("model"),
appliedFilterEl,
{ removeSilently: true },
);
}
//Remove the applied filter element from the page
$appliedFilterEl.remove();
},
this,
);
//Trigger the reset event on the Filters collection
this.filters.trigger("reset");
//Trigger the remove event on all the models now that they are all removed
_.invoke(removedFilters, "trigger", "remove");
//Toggle the applied filters header
this.toggleAppliedFiltersHeader();
},
/**
* Remove the applied filter element for the given model
* This only removed the element from the page, it doesn't update the model at all or
* trigger any events.
* @param {Filter} - The Filter model whose elements will be deleted
*/
removeAppliedFilterElByModel: function (filterModel) {
//Iterate over each applied filter element and find the matching filters
this.$(".applied-filter").each(function (i, el) {
if ($(el).data("model") == filterModel) {
//Remove the element from the page
$(el).remove();
}
});
//Toggle the applied filters header
this.toggleAppliedFiltersHeader();
},
},
);
return FilterGroupsView;
});