define([
"jquery",
"underscore",
"backbone",
"views/searchSelect/SearchableSelectView",
"models/LookupModel",
], function ($, _, Backbone, SearchableSelect, LookupModel) {
/**
* @class AccountSelectView
* @classdesc A select interface that allows the user to search for and select one or
* more accountIDs
* @classcategory Views/SearchSelect
* @extends SearchableSelect
* @constructor
* @since 2.15.0
* @screenshot views/searchSelect/AccountSelectViewView.png
*/
var AccountSelectView = SearchableSelect.extend(
/** @lends AccountSelectViewView.prototype */
{
/**
* The type of View this is
* @type {string}
* @since 2.15.0
*/
type: "AccountSelect",
/**
* The HTML class names for this view element
* @type {string}
* @since 2.15.0
*/
className: SearchableSelect.prototype.className + " account-select",
/**
* Text to show in the input field before any value has been entered
* @type {string}
* @default "Start typing a name"
* @since 2.15.0
*/
placeholderText: "Start typing a name",
/**
* Label for the input element
* @type {string}
* @default "Search for a person or group"
* @since 2.15.0
*/
inputLabel: "Search for a person or group",
/**
* Whether to allow users to select more than one value
* @type {boolean}
* @default true
* @since 2.15.0
*/
allowMulti: true,
/**
* Setting to true gives users the ability to add their own options that
* are not listed in this.options. This can work with either single
* or multiple search select dropdowns
* @type {boolean}
* @default true
*/
allowAdditions: true,
/**
* Can be set to an object to specify API settings for retrieving remote selection
* menu content from an API endpoint. Details of what can be set here are
* specified by the Semantic-UI / Fomantic-UI package. Set to false if not
* retrieving remote content.
* @type {Object|booealn}
* @since 2.15.0
* @see {@link https://fomantic-ui.com/modules/dropdown.html#remote-settings}
* @see {@link https://fomantic-ui.com/behaviors/api.html#/settings}
*/
apiSettings: {
// Use the Accounts lookup to search for a person or group when the user types
// something into the input
responseAsync: function (settings, callback) {
var view = $(this).data("view");
// The search term that the user has typed into the input
var searchTerm = settings.urlData.query;
// Only use the account lookup service is the user has typed at least two
// characters. Otherwise, the callback function is never called.
if (searchTerm.length < 2) {
callback({
success: false,
});
return;
}
// For search terms at least 2 characters long, use the Lookup Model
MetacatUI.appLookupModel.getAccountsAutocomplete(
{ term: searchTerm },
function (results) {
// If no match was found to the search term
if (results && results.length < 1) {
callback({
success: false,
});
return;
}
// If there were results, format for the semantic UI dropdown function
results = view.formatResults(results);
callback({
success: true,
results: results,
});
},
);
},
},
/**
* Creates a new SearchableSelectView
* @param {Object} options - A literal object with options to pass to the view
* @since 2.15.0
*/
initialize: function (options) {
try {
var view = this;
// Ensure there is a lookup model ready for this view to use.
if (MetacatUI.appLookupModel === "undefined") {
MetacatUI.appLookupModel = new LookupModel();
}
SearchableSelect.prototype.initialize.call(view, options);
} catch (e) {
console.log(
"Failed to initialize an Account Select view, error message:",
e,
);
}
},
/**
* Takes the results returned from from
* {@link LookupModel#getAccountsAutocomplete} and re-formats them for other
* functions. When the forTemplate argument is false, the results are formatted as
* a list mapping dropdown content specifically for the FomanticUI API. When
* forTemplate is true, then the results are formatted for use in the
* SearchableSelectView template: {@link SearchableSelectView#template}.
*
* @param {Object[]} results The response from
* {@link LookupModel#getAccountsAutocomplete}
* @param {boolean} forTemplate=false Whether or not to format the results for
* the {@link SearchableSelectView#template}
* @return {Object[]} The re-formatted results
*
* @see {@link https://fomantic-ui.com/modules/dropdown.html#remote-settings}
* @since 2.15.0
*/
formatResults: function (results, forTemplate = false) {
results = _.map(results, function (result) {
if (forTemplate) {
// Get the ID which is saved in the parentheses
var regExp = /\(([^)]+)\)$/;
var matches = regExp.exec(result.label);
//matches[1] contains the value between the parentheses
result.description = "Account ID: " + matches[1];
result.label = result.label.replace(regExp, "");
result.label = result.label.trim();
} else {
result.label = result.label.replace(
"(",
'<span class="description">',
);
result.label = result.label.replace(")", "</span>");
}
var icon = "";
if (result.type === "person") {
icon = "user";
}
if (result.type === "group") {
icon = "group";
}
if (icon) {
if (forTemplate) {
result.icon = icon;
} else {
result.label =
'<i class="icon icon-on-left icon-' +
icon +
'"></i>' +
result.label;
}
}
if (!forTemplate) {
result = {
name: result.label,
value: result.value,
};
}
return result;
});
return results;
},
/**
* Render the view
*
* @return {AccountSelectView} Returns the view
* @since 2.15.0
*/
render: function () {
var view = this;
// Use the account lookup service to match the pre-selected values to
// the account holder's name to use as a label.
// If we haven't started looking up user/organization names yet...
if (typeof view.labelsToFetch === "undefined") {
// Keep a count of the number of accounts we need to lookup
view.labelsToFetch = this.selected ? this.selected.length : 0;
if (view.labelsToFetch > 0) {
view.options = [];
view.selected.forEach(function (accountId) {
MetacatUI.appLookupModel.getAccountsAutocomplete(
{ term: accountId },
function (results) {
// The value should match only one account (since the value is an
// account ID). If we found the match, format it for the
// SearchableSelectView, and the icon and tooltip will automatically be
// added to the pre-selected labels.
if (results && results.length === 1) {
results = view.formatResults(results, true);
view.options.push(results[0]);
}
// Whether or not we found a match, count this lookup as complete
--view.labelsToFetch;
// Once we've looked up all of the accounts, call this render function
// again
if (view.labelsToFetch === 0) {
view.render();
}
},
);
});
return;
}
}
// Once we've fetched the labels for any pre-selected account IDs,
// render as usual
SearchableSelect.prototype.render.call(view);
},
/**
* addTooltip - Add a tooltip to a given element using the description in the
* options object that's set on the view.
*
* @param {HTMLElement} element The HTML element a tooltip should be added
* @param {string} position how to position the tooltip - top | bottom | left |
* right
* @return {jQuery} The element with a tooltip wrapped by jQuery
* @since 2.15.0
*/
addTooltip: function (element, position = "bottom") {
try {
if (!element) {
return;
}
// The account ID is saved in a <span> element in the label with the
// description class when the label is added from the list of search results
var descEl = $(element).find(".description");
var id = descEl.text();
descEl.remove();
// Otherwise, the ID is always saved as a data attribute
if (!id) {
id = $(element).attr("data-value");
}
// Show the account ID as a tooltip rather than in the label. Otherwise
// the input gets too crowded.
$(element)
.tooltip({
title: id ? "Account ID: " + id : "",
placement: position,
container: "body",
delay: {
show: 900,
hide: 50,
},
})
.on("show.bs.popover", function () {
var $el = $(this);
// Allow time for the popup to be added to the DOM
setTimeout(function () {
// Then add a special class to identify
// these popups if they need to be removed.
$el.data("tooltip").$tip.addClass("search-select-tooltip");
}, 10);
});
return $(element);
} catch (e) {
console.log(
"Failed to add tooltips in a searchable select view, error message: " +
e,
);
}
},
// TODO: We may want to add a custom is valid option to warn the user when
// a value entered cannot be found in the accounts lookup service.
// /**
// * isValidOption - Checks if a value is one of the values given in view.options
// *
// * @param {string} value The value to check
// * @return {boolean} returns true if the value is one of the values given in
// * view.options
// */
// isValidOption: function(value){
// },
},
);
return AccountSelectView;
});