/*global define */
define(['jquery', 'underscore', 'backbone', 'models/SolrHeader', 'models/SolrResult'],
function($, _, Backbone, SolrHeader, SolrResult) {
'use strict';
/**
@class SolrResults
@classdesc A collection of SolrResult models that represent a list of search results from the DataONE query service.
@extends Backbone.Collection
@classcategory Collections
@constructor
*/
var SolrResults = Backbone.Collection.extend(
/** @lends SolrResults.prototype */{
// Reference to this collection's model.
model: SolrResult,
/**
* The name of this type of collection.
* @type {string}
* @default "SolrResults"
* @since 2.25.0
*/
type: "SolrResults",
initialize: function(models, options) {
if( typeof options === "undefined" || !options ){
var options = {};
}
this.docsCache = options.docsCache || null;
this.currentquery = options.query || '*:*';
this.rows = options.rows || 25;
this.start = options.start || 0;
this.sort = options.sort || 'dateUploaded desc';
this.facet = options.facet || [];
this.facetCounts = "nothing";
this.stats = options.stats || false;
this.minYear = options.minYear || 1900;
this.maxYear = options.maxYear || new Date().getFullYear();
this.queryServiceUrl = options.queryServiceUrl || MetacatUI.appModel.get('queryServiceUrl');
if( MetacatUI.appModel.get("defaultSearchFields")?.length )
this.fields = MetacatUI.appModel.get("defaultSearchFields").join(",");
else
this.fields = options.fields || "id,title";
//If POST queries are disabled in the whole app, don't use POSTs here
if( MetacatUI.appModel.get("disableQueryPOSTs") ){
this.usePOST = false;
}
//If this collection was initialized with the usePOST option, use POSTs here
else if( options.usePOST ){
this.usePOST = true;
}
//Otherwise default to using GET
else{
this.usePOST = false;
}
},
url: function() {
//Convert facet keywords to a string
var facetFields = "";
this.facet = _.uniq(this.facet);
for (var i=0; i<this.facet.length; i++){
facetFields += "&facet.field=" + this.facet[i];
}
// limit to matches
if (this.facet.length > 0) {
facetFields += "&facet.mincount=1"; // only facets meeting the current search
facetFields += "&facet.limit=-1"; // CAREFUL: -1 means no limit on the number of facets
}
//Do we need stats?
if (!this.stats){
var stats = "";
}
else{
var stats = "&stats=true";
for(var i=0; i<this.stats.length; i++){
stats += "&stats.field=" + this.stats[i];
}
}
//create the query url
var endpoint = (this.queryServiceUrl || MetatcatUI.appModel.get("queryServiceUrl")) + "q=" + this.currentquery;
if(this.fields)
endpoint += "&fl=" + this.fields;
if(this.sort)
endpoint += "&sort=" + this.sort;
if( typeof this.rows == "number" || (typeof this.rows == "string" && this.rows.length))
endpoint += "&rows=" + this.rows;
if( typeof this.start == "number" || (typeof this.start == "string" && this.start.length))
endpoint += "&start=" + this.start;
if( this.facet.length > 0 )
endpoint += "&facet=true&facet.sort=index" + facetFields;
endpoint += stats + "&wt=json";
return endpoint;
},
parse: function(solr) {
//Is this our latest query? If not, use our last set of docs from the latest query
if((decodeURIComponent(this.currentquery).replace(/\+/g, " ") != solr.responseHeader.params.q) && this.docsCache)
return this.docsCache;
if(!solr.response){
if(solr.error && solr.error.msg){
console.log("Solr error: " + solr.error.msg);
}
return
}
//Save some stats
this.header = new SolrHeader(solr.responseHeader);
this.header.set({"numFound" : solr.response.numFound});
this.header.set({"start" : solr.response.start});
this.header.set({"rows" : solr.responseHeader.params.rows});
//Get the facet counts and store them in this model
if (solr.facet_counts) {
this.facetCounts = solr.facet_counts.facet_fields;
} else {
this.facetCounts = "nothing";
}
//Cache this set of results
this.docsCache = solr.response.docs;
return solr.response.docs;
},
/**
* Fetches the next page of results
*/
nextpage: function() {
// Only increment the page if the current page is not the last page
if (this.start + this.rows < this.header.get("numFound")) {
this.start += this.rows;
}
if (this.header != null) {
this.header.set({"start" : this.start});
}
this.lastUrl = this.url();
var fetchOptions = this.createFetchOptions();
this.fetch(fetchOptions);
},
/**
* Fetches the previous page of results
*/
prevpage: function() {
this.start -= this.rows;
if (this.start < 0) {
this.start = 0;
}
if (this.header != null) {
this.header.set({"start" : this.start});
}
this.lastUrl = this.url();
var fetchOptions = this.createFetchOptions();
this.fetch(fetchOptions);
},
/**
* Fetches the given page of results
* @param {number} page
*/
toPage: function(page) {
// go to the requested page
var requestedStart = this.rows * page;
/*
if (this.header != null) {
if (requestedStart < this.header.get("numFound")) {
this.start = requestedStart;
}
this.header.set({"start" : this.start});
}*/
this.start = requestedStart;
this.lastUrl = this.url();
var fetchOptions = this.createFetchOptions();
this.fetch(fetchOptions);
},
setrows: function(numrows) {
this.rows = numrows;
},
query: function(newquery) {
if(typeof newquery != "undefined" && this.currentquery != newquery){
this.currentquery = newquery;
this.start = 0;
}
this.lastUrl = this.url();
var fetchOptions = this.createFetchOptions();
this.fetch(fetchOptions);
},
setQuery: function(newquery) {
if (this.currentquery != newquery) {
this.currentquery = newquery;
this.start = 0;
this.lastQuery = newquery;
}
},
/**
* Returns the last query that was fetched.
* @returns {string}
*/
getLastQuery: function(){
return this.lastQuery;
},
setfields: function(newfields) {
this.fields = newfields;
},
setSort: function(newsort) {
this.sort = newsort;
this.trigger("change:sort");
},
setFacet: function (fields) {
if (!Array.isArray(fields)) {
fields = [fields];
}
this.facet = fields;
this.trigger("change:facet");
},
setStats: function(fields){
this.stats = fields;
},
createFetchOptions: function(){
var options = {
start : this.start,
reset: true
}
let usePOST = this.usePOST || (this.currentquery.length > 1500 && !MetacatUI.appModel.get("disableQueryPOSTs"));
if( usePOST ){
options.type = "POST";
var queryData = new FormData();
queryData.append("q", decodeURIComponent(this.currentquery));
queryData.append("rows", this.rows);
queryData.append("sort", this.sort.replace("+", " "));
queryData.append("fl", this.fields);
queryData.append("start", this.start);
queryData.append("wt", "json");
//Add the facet fields to the FormData
if( this.facet.length ){
queryData.append("facet", "true");
for (var i=0; i<this.facet.length; i++){
queryData.append("facet.field", this.facet[i]);
}
queryData.append("facet.mincount", "1");
queryData.append("facet.limit", "-1");
queryData.append("facet.sort", "index");
}
//Add stats to the FormData
if( this.stats.length ){
queryData.append("stats", "true");
for(var i=0; i<this.stats.length; i++){
queryData.append("stats.field", this.stats[i]);
}
}
options.data = queryData;
options.contentType = false;
options.processData = false;
options.dataType = "json";
options.url = MetacatUI.appModel.get("queryServiceUrl");
}
return _.extend(options, MetacatUI.appUserModel.createAjaxSettings());
},
/**
* Returns the total number of results that were just fetched, or undefined if nothing has been fetched yet
* @since 2.22.0
* @returns {number|undefined}
*/
getNumFound: function(){
return this.header?.get("numFound");
},
/**
* Calculates and returns the total pages of results that was just fetched
* @since 2.22.0
* @returns {number}
*/
getNumPages: function(){
let total = this.getNumFound();
if(total){
return Math.ceil(total/this.header.get("rows"))-1; //-1 because our pages are zero-based numbered (where page 0 gets the first n results)
}
else{
return 0;
}
},
/**
* Calculates and returns the current page of results that was just fetched
* @since 2.22.0
* @returns {number}
*/
getCurrentPage: function(){
if(this.header?.get("start") && this.header?.get("rows")){
return Math.ceil(this.header.get("start")/this.header.get("rows"));
}
else{
return 0;
}
},
/**
* Returns the index number of the first search result E.g. the first page of results may be 0-24, where 0 is the start.
* @since 2.22.0
* @returns {number}
*/
getStart: function(){
if(this.header){
return this.header.get("start");
}
else{
return this.start;
}
},
/**
* Calculates the index number of the last search result. E.g. the first page of results may be 0-24, where 24 is the end.
* @since 2.22.0
* @returns {number}
*/
getEnd: function(){
return parseInt(this.getStart()) + parseInt(this.getRows()) - 1; // -1 since it is zero-based numbering
},
/**
* Returns the number of search result rows
* @since 2.22.0
* @returns {number}
*/
getRows: function(){
if(this.header){
return this.header.get("rows");
}
else{
return this.rows;
}
},
/**
* Gets and returns the URL string that was sent during the last fetch.
* @since 2.22.0
* @returns {string}
*/
getLastUrl: function(){
return this.lastUrl || "";
},
/**
* Get the list of PIDs for the search results
* @returns {string[]} - The list of PID strings for the search results
* @since 2.25.0
*/
getPIDs: function () {
return this.pluck("id");
},
/**
* Determines whether the search parameters have changed since the last fetch. Returns true the next URL
* to be sent in a fetch() is different at all from the last url that was fetched.
* @since 2.22.0
* @returns {boolean}
*/
hasChanged: function(){
return this.url() != this.getLastUrl();
}
});
return SolrResults;
});