define(['underscore', 'jquery', 'backbone', "models/Map", "models/CollectionModel", "models/Search", "views/DataCatalogViewWithFilters", "views/queryBuilder/QueryBuilderView", "text!templates/editCollection.html"], function(_, $, Backbone, Map, CollectionModel, Search, DataCatalogViewWithFilters, QueryBuilder, Template){ /** * @class EditCollectionView * @classdesc A view that allows the user to edit the search filters that define their dataset collection * @classcategory Views * @extends Backbone.View * @constructor */ var EditCollectionView = Backbone.View.extend( /** @lends EditCollectionView.prototype */{ /** * The type of View this is * @type {string} */ type: "EditCollection", /** * A reference to the parent editor view, if there is one * @type {PortalEditorView} */ editorView: undefined, /** * The HTML tag name to use for this view's element * @type {string} */ tagName: "div", /** * The HTML classes to use for this view's element * @type {string} */ className: "edit-collection", /** * The Collection model that is being edited * @type {CollectionModel} */ model: undefined, /** * The template for this view. An HTML file is converted to an Underscore.js template */ template: _.template(Template), /** * A jQuery selector for the element that the DataCatalogViewWithFilters should be inserted into * @type {string} */ dataCatalogViewContainer: ".data-catalog-view-container", /** * A jQuery selector for the element that the Save and Cancel buttons should be inserted into * @type {string} */ collectionControlsContainer: ".applied-filters-container", /** * A jQuery selector for the element that the QueryBuilder should be inserted into * @type {string} */ queryBuilderViewContainer: ".query-builder-view-container", /** * A jQuery selector for the element that contains the filter help text * @type {string} */ helpTextContainer: "#filter-help-text", /** * An array of hex color codes used to help distinguish between different rules * @type {string[]} */ ruleColorPalette: ["#44AA99", "#137733", "#c9a538", "#CC6677", "#882355", "#AA4499","#332288"], /** * Query fields to exclude in the metadata field selector of each Query Rule. This * is a list of field names that exist in the query service index (i.e. Solr), but * which should be hidden in the Query Builder * @type {string[]} */ queryBuilderExcludeFields: MetacatUI.appModel.get("collectionQueryExcludeFields"), /** * Query fields to exclude in the metadata field selector for any Query Rules that are * in nested Query Builders (i.e. in nested Filter Groups). This is a list of field * names that exist in the query service index (i.e. Solr), but which should be hidden * in nested Query Builders * @type {string[]} */ queryBuilderNestedExcludeFields: _.union( MetacatUI.appModel.get("collectionQueryExcludeFields"), MetacatUI.appModel.get("queryIdentifierFields") ), /** * Query fields that do not exist in the query service index, but which we would * like to show as options in the Query Builder field input. * @type {SpecialField[]} * @since 2.15.0 */ queryBuilderSpecialFields: MetacatUI.appModel.get("collectionQuerySpecialFields"), /** * The events this view will listen to and the associated function to call. * @type {Object} */ events: { }, /** * Is exexcuted when a new EditCollectionView is created * @param {Object} options - A literal object with options to pass to the view * @property {CollectionModel} options.model - The collection whose search results will be displayed and edited in this view */ initialize: function(options){ if( typeof options == "object" ){ this.model = options.model || undefined; } }, /** * Renders this view */ render: function(){ var title = "Change the data in your collection" if(this.model.isNew()){ title = "Add data to your collection" } var helpText = "", email = MetacatUI.appModel.get("emailContact"); if (email) { helpText = 'Need help building your data collection? Get in touch.' } this.$el.html(this.template({ title: title, description: "Your collection can include any of the datasets that are available on the network. " + "Build rules based on metadata to define which datasets should be included in your collection. " + "Data added to the network in the future that match these rules will also be added to your collection. " + "Click the save button when you're happy with the results.", helpText: helpText })); // Remove this when the Query Builder is no longer new: this.$el .find(".edit-collection-title") .append($(' NEW ')); // .append($('NEW!')); // Make sure that we have a series ID before we render the Data Catalog // View With Filters. For new portals, we generate and reserve a series ID // and use it to add an isPartOf filter to the portal model. This takes time, // and influences the search results shown in the data catalog. if( this.model.get("seriesId") || this.model.get("latestVersion") ){ //Render the DataCatalog this.renderDataCatalog(); } else { //When the seriesId or latest pid version is found, render the DataCatalog this.listenToOnce(this.model, "change:seriesId", this.renderDataCatalog); this.listenToOnce(this.model, "latestVersionFound", this.renderDataCatalog); } //this.renderCollectionControls(); }, /** * renderQueryBuilder - Render the QueryBuilder and insert it into this view */ renderQueryBuilder: function(){ // If the isPartOf filter is hidden, then don't allow users to build // a Query Rule using the isPartOf field. If they do, that rule will // be hidden the next time they open the portal in the editor. Also, // the filter they create will overwrite the isPartOf filter created by // default. if(MetacatUI.appModel.get("hideIsPartOfFilter") === true ? true : false){ this.queryBuilderExcludeFields.push("isPartOf") } var queryBuilder = new QueryBuilder({ filterGroup: this.model.get("definition"), ruleColorPalette: this.ruleColorPalette, excludeFields: this.queryBuilderExcludeFields, nestedExcludeFields: this.queryBuilderNestedExcludeFields, specialFields: this.queryBuilderSpecialFields, }); // Render the Query Builder and insert it into this view this.$(this.queryBuilderViewContainer).html(queryBuilder.el); queryBuilder.render(); }, /** * Render the DataCatalogViewWithFilters */ renderDataCatalog: function(){ this.renderQueryBuilder(); var searchModel = this.model.get("searchModel"); searchModel.set("useGeohash", false); // Create a DataCatalog view var dataCatalogView = new DataCatalogViewWithFilters({ searchModel: searchModel, searchResults: this.model.get("searchResults"), mapModel: this.model.get("mapModel") || new Map(), isSubView: true, mode: "map", filters: false, solrError500Message: "There may be a problem with one of the rules you created." + " Try undoing the last change you made.", solrErrorTitle: "Something went wrong searching for datasets that match your query", // Override the function that creates filter groups on the left of the // data catalog view. With the Query Builder view, they are not needed. // Otherwise, the defaultFilterGroups will be added to the Query Builder createFilterGroups: function(){ return }, addAnnotationFilter: function(){ return }, editorView: this.editorView }); //Render the view and insert it into the page this.$(this.dataCatalogViewContainer).html(dataCatalogView.el); dataCatalogView.render(); this.listenTo(this.model.get("searchResults"), "reset", this.toggleHelpText); }, /** * Renders the edit collection controls - e.g. a Save and Cancel buttton */ renderCollectionControls: function(){ //Create a Save button var saveButton = $(document.createElement("a")) .addClass("save btn btn-primary") .text("Save"), //Create a Cancel button cancelButton = $(document.createElement("a")) .addClass("cancel btn") .text("Cancel"), //Create a container for the buttons buttons = $(document.createElement("div")) .addClass("collection-controls") .append(saveButton, cancelButton); //Add the buttons to the view this.$(this.collectionControlsContainer).append(buttons); }, /** * Either hides or shows the help message that lets the user know * they can add filters when the collection is empty. */ toggleHelpText: function() { //Get the list of filters currently applied to the collection definition var currentFilters = this.model.get("definitionFilters"), msg = ""; // If there are no filters set at all, the entire repository catalog will be listed as // search results, so display a helpful message if ( currentFilters.length == 0 && this.model.get("searchResults").length ) { msg = "
Your dataset collection hasn't been created yet.
" + "

The datasets listed here are totally unfiltered. To specify which datasets belong to your collection, " + "add rules in the Query Builder above.

"; } //If there is only an isPartOf filter, but no datasets have been marked as part of this collection else if( currentFilters.length == 1 && currentFilters.models[0].get("fields")[0] == "isPartOf" && !this.model.get("searchResults").length){ msg = "
Your dataset collection is empty.
" + "

To add datasets to your collection, " + "add rules in query builder above.

"; //TODO: When the ability to add datasets to collection via the "isPartOf" relationship is added to MetacatUI // then update this message with details on how to add datasets to the collection } //If a message string was created, display it if( msg ){ //Show the message MetacatUI.appView.showAlert(msg, "alert-warning", this.$(this.helpTextContainer)); } else{ //Remove the message this.$(this.helpTextContainer).empty(); //Remove validation messaging, too this.$(".notification.error[data-category='definition']").removeClass("error").empty(); } } }); return EditCollectionView; });