/*global define */
define(["jquery",
"backbone",
"collections/maps/MapAssets",
"models/filters/FilterGroup",
"models/connectors/Filters-Search",
"models/connectors/Geohash-Search",
"models/maps/assets/CesiumGeohash",
"models/maps/Map",
"views/search/SearchResultsView",
"views/filters/FilterGroupsView",
"views/maps/MapView",
"views/search/SearchResultsPagerView",
"views/search/SorterView"
],
function($, Backbone, MapAssets, FilterGroup, FiltersSearchConnector, GeohashSearchConnector, CesiumGeohash, Map, SearchResultsView, FilterGroupsView, MapView, PagerView, SorterView){
"use strict";
/**
* @class CatalogSearchView
* @name CatalogSearchView
* @classcategory Views
* @extends Backbone.View
* @constructor
* @since 2.22.0
*/
return Backbone.View.extend(
/** @lends CatalogSearchView.prototype */ {
/**
* The type of View this is
* @type {string}
*/
type: "CatalogSearch",
/**
* The HTML tag to use for this view's element
* @type {string}
*/
tagName: "section",
/**
* The HTML classes to use for this view's element
* @type {string}
*/
className: "catalog-search-view",
template: `
<section class="catalog-search-inner">
<div class="filter-groups-container"></div>
<div class="search-results-panel-container">
<div class="map-toggle-container"><a class="toggle-map"><i class="icon icon-double-angle-left"></i> Show Map</a></div>
<div class="title-container"></div>
<div class="pager-container"></div>
<div class="sorter-container"></div>
<div class="search-results-container"></div>
</div>
<div class="map-panel-container">
<div class="map-toggle-container"><a class="toggle-map">Hide Map <i class="icon icon-double-angle-right"></i></a></div>
<div class="map-container"></div>
</div>
</section>
`,
/**
* The search mode to use. This can be set to either `map` or `list`. List mode will hide all map features.
* @type string
* @since 2.22.0
* @default "map"
*/
mode: "map",
searchResults: null,
filterGroupsView: null,
/**
* @type {PagerView}
*/
pagerView: null,
/**
* @type {SorterView}
*/
sorterView: null,
searchModel: null,
allFilters: null,
filterGroups: null,
/**
* An array of literal objects to transform into FilterGroup models. These FilterGroups will be displayed in this view and used for searching. If not provided, the {@link AppConfig#defaultFilterGroups} will be used.
* @type {FilterGroup#defaults[]}
*/
filterGroupsJSON: null,
/**
* The jQuery selector for the FilterGroupsView container
* @type {string}
*/
filterGroupsContainer: ".filter-groups-container",
/**
* The query selector for the SearchResultsView container
* @type {string}
*/
searchResultsContainer: ".search-results-container",
/**
* The query selector for the CesiumWidgetView container
* @type {string}
*/
mapContainer: ".map-container",
/**
* The query selector for the PagerView container
* @type {string}
*/
pagerContainer: ".pager-container",
/**
* The query selector for the SorterView container
* @type {string}
*/
sorterContainer: ".sorter-container",
/**
* The query selector for the title container
* @type {string}
*/
titleContainer: ".title-container",
/**
* The events this view will listen to and the associated function to call.
* @type {Object}
*/
events: {
"click .map-toggle-container" : "toggleMode"
},
render: function(){
//Set the search mode - either map or list
this.setMode();
//Set up the view for styling and layout
this.setupView();
//Set up the search and search result models
this.setupSearch();
//Render the search components
this.renderComponents();
},
/**
* Sets up the basic components of this view
*/
setupView: function(){
document.querySelector("body").classList.add(`catalog-search-body`, `${this.mode}Mode`);
//Add LinkedData to the page
this.addLinkedData();
this.$el.html(this.template);
},
/**
* Sets the search mode (map or list)
* @since 2.22.0
*/
setMode: function(){
//Get the search mode - either "map" or "list"
if (((typeof this.mode === "undefined") || !this.mode) && (MetacatUI.appModel.get("enableCesium"))) {
this.mode = "map";
}
// Use map mode on tablets and browsers only
if ($(window).outerWidth() <= 600) {
this.mode = "list";
}
},
renderComponents: function(){
this.renderFilters();
//Render the list of search results
this.renderSearchResults();
//Render the Title
this.renderTitle();
this.listenTo(this.searchResultsView.searchResults, "reset", this.renderTitle);
//Render Pager
this.renderPager();
//Render Sorter
this.renderSorter();
//Render Cesium
this.renderMap();
},
/**
* Renders the search filters
* @since 2.22.0
*/
renderFilters: function(){
//Render FilterGroups
this.filterGroupsView = new FilterGroupsView({
filterGroups: this.filterGroups,
filters: this.connector?.get("filters"),
vertical: true,
parentView: this
});
//Add the FilterGroupsView element to this view
this.$(this.filterGroupsContainer).html(this.filterGroupsView.el);
//Render the FilterGroupsView
this.filterGroupsView.render();
},
/**
* Creates the SearchResultsView and saves a reference to the SolrResults collection
* @since 2.22.0
*/
createSearchResults: function(){
this.searchResultsView = new SearchResultsView();
if( this.connector ){
this.searchResultsView.searchResults = this.connector.get("searchResults");
}
},
/**
* Renders the search result list
* @since 2.22.0
*/
renderSearchResults: function(){
if(!this.searchResultsView) return;
//Add the view element to this view
this.$(this.searchResultsContainer).html(this.searchResultsView.el);
//Render the view
this.searchResultsView.render();
},
/**
* Creates a PagerView and adds it to the page.
* @since 2.22.0
*/
renderPager: function(){
this.pagerView = new PagerView();
//Give the PagerView the SearchResults to listen to and update
this.pagerView.searchResults = this.searchResultsView.searchResults;
//Add the pager view to the page
this.el.querySelector(this.pagerContainer).replaceChildren(this.pagerView.el);
//Render the pager view
this.pagerView.render();
},
/**
* Creates a SorterView and adds it to the page.
* @since 2.22.0
*/
renderSorter: function(){
this.sorterView = new SorterView();
//Give the SorterView the SearchResults to listen to and update
this.sorterView.searchResults = this.searchResultsView.searchResults;
//Add the sorter view to the page
this.el.querySelector(this.sorterContainer).replaceChildren(this.sorterView.el);
//Render the sorter view
this.sorterView.render();
},
/**
* Constructs an HTML string of the title of this view
* @param {number} start
* @param {number} end
* @param {number} numFound
* @returns {string}
* @since 2.22.0
*/
titleTemplate: function(start, end, numFound){
let html = `<div id="statcounts"><h5 class="result-header-count bold-header" id="countstats"><span>${MetacatUI.appView.commaSeparateNumber(start)}</span> to <span>${MetacatUI.appView.commaSeparateNumber(end)}</span>`;
if(typeof numFound == "number"){
html += ` of <span>${MetacatUI.appView.commaSeparateNumber(numFound)}</span>`;
}
html += `</h5></div>`;
return html;
},
/**
* Updates the view title using the {@link CatalogSearchView#searchResults} data.
* @since 2.22.0
*/
renderTitle: function(){
let titleEl = this.el.querySelector(this.titleContainer);
if(!titleEl){
titleEl = document.createElement("div");
titleEl.classList.add("title-container");
this.el.prepend(titleEl);
}
titleEl.innerHTML="";
let title = this.titleTemplate(this.searchResultsView.searchResults.getStart()+1, this.searchResultsView.searchResults.getEnd()+1, this.searchResultsView.searchResults.getNumFound());
titleEl.insertAdjacentHTML("beforeend", title);
},
/**
* Creates the Filter models and SolrResults that will be used for searches
* @since 2.22.0
*/
setupSearch: function(){
//Get an array of all Filter models
let allFilters = [];
this.filterGroups = this.createFilterGroups();
this.filterGroups.forEach(group => {
allFilters = allFilters.concat(group.get("filters")?.models);
});
//Connect the filters to the search and search results
let connector = new FiltersSearchConnector({ filtersList: allFilters });
this.connector = connector;
connector.startListening();
this.createSearchResults();
this.createMap();
},
/**
* Creates UI Filter Groups. UI Filter Groups
* are custom, interactive search filter elements, grouped together in one
* panel, section, tab, etc.
* @param {FilterGroup#defaults[]} filterGroupsJSON An array of literal objects to transform into FilterGroup models. These FilterGroups will be displayed in this view and used for searching. If not provided, the {@link AppConfig#defaultFilterGroups} will be used.
* @since 2.22.0
*/
createFilterGroups: function(filterGroupsJSON=this.filterGroupsJSON){
try{
//Start an array for the FilterGroups and the individual Filter models
let filterGroups = [];
//Iterate over each default FilterGroup in the app config and create a FilterGroup model
(filterGroupsJSON || MetacatUI.appModel.get("defaultFilterGroups")).forEach( filterGroupJSON => {
//Create the FilterGroup model
//Add to the array
filterGroups.push(new FilterGroup(filterGroupJSON));
});
return filterGroups;
}
catch(e){
console.error("Couldn't create Filter Groups in search. ", e)
}
},
/**
* Create the models and views associated with the map and map search
* @since 2.22.0
*/
createMap: function(){
let mapOptions = Object.assign({}, MetacatUI.appModel.get("catalogSearchMapOptions") || {});
let map = new Map(mapOptions);
//Add a CesiumGeohash layer to the map
/* let geohashLayer = new CesiumGeohash();
geohashLayer.
let assets = map.get("layers");
assets.add(geohashLayer);
*/
let geohashLayer = map.get("layers").findWhere({type: "CesiumGeohash"})
//Connect the CesiumGeohash to the SolrResults
let connector = new GeohashSearchConnector({
cesiumGeohash: geohashLayer,
searchResults: this.searchResultsView.searchResults
});
connector.startListening();
this.geohashSearchConnector = connector;
//Set the geohash level for the search
if( Array.isArray(this.searchResultsView.searchResults.facet) )
this.searchResultsView.searchResults.facet.push("geohash_" + geohashLayer.get("geohashLevel"));
else
this.searchResultsView.searchResults.facet = "geohash_" + geohashLayer.get("geohashLevel");
//Create the Map model and view
this.mapView = new MapView({ model: map });
},
/**
* Renders the Cesium map with a geohash layer
* @since 2.22.0
*/
renderMap: function(){
//Add the map to the page and render it
this.$(this.mapContainer).empty().append(this.mapView.el);
this.mapView.render();
},
/**
* Linked Data Object for appending the jsonld into the browser DOM
* @since 2.22.0
* */
addLinkedData: function() {
// JSON Linked Data Object
let elJSON = {
"@context": {
"@vocab": "http://schema.org/"
},
"@type": "DataCatalog",
}
// Find the MN info from the CN Node list
let members = MetacatUI.nodeModel.get("members"),
nodeModelObject;
for (let i = 0; i < members.length; i++) {
if (members[i].identifier == MetacatUI.nodeModel.get("currentMemberNode")) {
nodeModelObject = members[i];
}
}
if (nodeModelObject) {
// "keywords": "",
// "provider": "",
let conditionalData = {
"description": nodeModelObject.description,
"identifier": nodeModelObject.identifier,
"image": nodeModelObject.logo,
"name": nodeModelObject.name,
"url": nodeModelObject.url
}
$.extend(elJSON, conditionalData);
}
// Check if the jsonld already exists from the previous data view
// If not create a new script tag and append otherwise replace the text for the script
if (!document.getElementById("jsonld")) {
var el = document.createElement("script");
el.type = "application/ld+json";
el.id = "jsonld";
el.text = JSON.stringify(elJSON);
document.querySelector("head").appendChild(el);
} else {
var script = document.getElementById("jsonld");
script.text = JSON.stringify(elJSON);
}
},
/**
* Toggles between map and list search mode
* @since 2.22.0
*/
toggleMode: function(){
let classList = document.querySelector("body").classList;
if(this.mode == "map"){
this.mode = "list";
classList.remove("mapMode");
classList.add("listMode")
}
else{
this.mode = "map";
classList.remove("listMode");
classList.add("mapMode")
}
},
onClose: function(){
document.querySelector("body").classList.remove(`catalog-search-body`, `${this.mode}Mode`);
//Remove the JSON-LD from the page
document.getElementById("jsonld")?.remove();
}
});
});