define([
"jquery",
"underscore",
"backbone",
"models/filters/FilterGroup",
"views/filters/FilterView",
"views/filters/BooleanFilterView",
"views/filters/ChoiceFilterView",
"views/filters/DateFilterView",
"views/filters/NumericFilterView",
"views/filters/ToggleFilterView",
"views/searchSelect/AnnotationFilterView",
"views/searchSelect/SearchableSelectView",
"views/filters/SemanticFilterView",
], function (
$,
_,
Backbone,
FilterGroup,
FilterView,
BooleanFilterView,
ChoiceFilterView,
DateFilterView,
NumericFilterView,
ToggleFilterView,
AnnotationFilterView,
SearchableSelectView,
SemanticFilterView,
) {
"use strict";
/**
* @class FilterGroupView
* @classdesc Renders a display of a group of filters
* @classcategory Views/Filters
* @extends Backbone.View
*/
var FilterGroupView = Backbone.View.extend(
/** @lends FilterGroupView.prototype */ {
/**
* A FilterGroup model to be rendered in this view
* @type {FilterGroup} */
model: null,
/**
* A reference to the PortalEditorView
* @type {PortalEditorView}
*/
editorView: undefined,
subviews: new Array(),
tagName: "div",
className: "filter-group tab-pane",
/**
* Set to true to render this view as a FilterGroup editor; allow the user to add,
* delete, and edit filters within this group.
* @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,
initialize: function (options) {
if (!options || typeof options != "object") {
var options = {};
}
this.model = options.model || new FilterGroup();
this.editorView = options.editorView || null;
this.subviews = new Array();
if (options.edit === true) {
this.edit = true;
}
if (options.collapsible && typeof options.collapsible === "boolean") {
this.collapsible = options.collapsible;
}
},
render: function () {
var view = this;
//Add the id attribute from the filter group label
this.$el.attr(
"id",
this.model.get("label").replace(/([^a-zA-Z0-9])/g, ""),
);
//Attach a reference to this view to the element
this.$el.data("view", this);
// Get the collection of filters from the FilterGroup model
var filters = this.model.get("filters");
var filtersRow = $(document.createElement("div")).addClass(
"filters-container",
);
this.$el.append(filtersRow);
// If this is a FilterGroup editor, pass the "edit" status on to the Filter Views
// so that a user can make changes to filters in this group.
var filterMode = this.edit ? "edit" : "regular";
//Render each filter model in the FilterGroup model
filters.each(function (filter, i) {
// The options to pass on to every FilterView
var viewOptions = {
model: filter,
mode: filterMode,
editorView: this.editorView,
collapsible: this.collapsible,
};
//Some filters are handled specially
//The isPartOf filter should be rendered as a ToggleFilter
if (filter.get && filter.get("fields").includes("isPartOf")) {
//Set a trueValue on the model so it works with the ToggleView
if (filter.get("values").length && filter.get("values")[0]) {
filter.set("trueValue", filter.get("values")[0]);
}
//Create a ToggleView
var filterView = new ToggleFilterView(viewOptions);
} else if (
view.areAllFieldsSemantic(filter.get("fields")) &&
MetacatUI.appModel.get("bioportalAPIKey") &&
filter.type === "Filter"
) {
var filterView = new SemanticFilterView(viewOptions);
} else {
//Depending on the filter type, create a filter view
switch (filter.type) {
case "Filter":
var filterView = new FilterView(viewOptions);
break;
case "BooleanFilter":
// TODO: Set up "edit" and "uiBuilder" mode for BooleanFilters
var filterView = new BooleanFilterView(viewOptions);
break;
case "ChoiceFilter":
var filterView = new ChoiceFilterView(viewOptions);
break;
case "DateFilter":
var filterView = new DateFilterView(viewOptions);
break;
case "NumericFilter":
// TODO: Set up "edit" and "uiBuilder" mode for numeric filters
var filterView = new NumericFilterView({ model: filter });
break;
case "ToggleFilter":
var filterView = new ToggleFilterView(viewOptions);
break;
default:
var filterView = new FilterView(viewOptions);
}
}
//Render the view and append it's element to this view
filterView.render();
//Append the filter view element to the view el
filtersRow.append(filterView.el);
//Save a reference to this subview
this.subviews.push(filterView);
}, this);
// Insert a button to add a new Filter model if this view is in edit mode
if (this.edit) {
require(["views/filters/FilterEditorView"], function (FilterEditor) {
var addFilterButton = new FilterEditor({
collection: view.model.get("filters"),
mode: "edit",
isNew: true,
editorView: view.editorView,
});
// Render the view and append it's element to this view
addFilterButton.render();
// Append the filter view element to the view el
filtersRow.append(addFilterButton.el);
// Save a reference to this subview
view.subviews.push(addFilterButton);
});
}
},
/**
* Actions to perform after the render() function has completed and this view's
* element is added to the webpage.
*/
postRender: function () {
//Iterate over each subview and call postRender() if it exists
_.each(this.subviews, function (subview) {
if (subview.postRender) {
subview.postRender();
}
});
},
/**
* Helper function to check whether or not a set of query field names are
* all semantic fields.
*
* Checks the array "fields"
*
* @param {string[]} fields The list of query fields to check
* @return {boolean} Whether or not all members of fields are semantic.
* Returns true only when fields and AppModel.querySemanticFields are
* non-zero in length and all values of fields are present in
* AppModel.querySemanticFields.
*/
areAllFieldsSemantic: function (fields) {
if (!fields || !fields.length) {
return false;
}
var querySemanticFields = MetacatUI.appModel.get("querySemanticFields");
if (!querySemanticFields) {
return false;
}
return fields.every(function (field) {
return querySemanticFields.includes(field);
});
},
},
);
return FilterGroupView;
});