define(["underscore", "jquery", "backbone", "models/AccessRule"], function (
_,
$,
Backbone,
AccessRule,
) {
/**
* @class AccessRuleView
* @classdesc Renders a single access rule from an object's access policy
* @classcategory Views
* @screenshot views/AccessRuleView.png
* @extends Backbone.View
*/
var AccessRuleView = Backbone.View.extend(
/** @lends AccessRuleView.prototype */ {
/**
* The type of View this is
* @type {string}
*/
type: "AccessRule",
/**
* The HTML tag name for this view's element
* @type {string}
*/
tagName: "tr",
/**
* The HTML classes to use for this view's element
* @type {string}
*/
className: "access-rule",
/**
* The AccessRule model that is displayed in this view
* @type {AccessRule}
*/
model: undefined,
/**
* If true, this view represents a new AccessRule that hasn't been added to the AccessPolicy yet
* @type {boolean}
*/
isNew: false,
/**
* If true, the user can change the AccessRule via this view.
* If false, the AccessRule will just be displayed.
* @type {boolean}
*/
allowChanges: true,
/**
* The events this view will listen to and the associated function to call.
* @type {Object}
*/
events: {
"keypress .search input": "listenForEnter",
"click .add.icon": "updateModel",
"change .access select": "updateModel",
},
/**
* Is executed when a new AccessRuleView is created
* @param {Object} options - A literal object with options to pass to the view
*/
initialize: function (options) {},
/**
* Renders a single Access Rule
*/
render: function () {
try {
this.$el.empty();
//If there's no model, exit now since there's nothing to render
if (!this.model) {
return;
}
//Get the subjects that should be hidden
var hiddenSubjects = MetacatUI.appModel.get(
"hiddenSubjectsInAccessPolicy",
);
//If this AccessRule is for a subject that should be hidden,
if (
Array.isArray(hiddenSubjects) &&
_.contains(hiddenSubjects, this.model.get("subject"))
) {
var usersGroups = _.pluck(
MetacatUI.appUserModel.get("isMemberOf"),
"groupId",
);
//If the current user is not part of this hidden group or is not the hidden user
if (
!_.contains(
hiddenSubjects,
MetacatUI.appUserModel.get("username"),
) &&
!_.intersection(hiddenSubjects, usersGroups).length
) {
//Remove this view
this.remove();
//Exit
return;
}
}
if (this.isNew) {
//If we aren't allowing changes to this AccessRule, then don't display
// anything for new AcccessRule rows
if (!this.allowChanges) {
return;
}
this.$el.addClass("new");
//Create a text input for adding a subject or name
var label = $(document.createElement("label"))
.attr("for", "search")
.text("Search by name, ORCID, or group name")
.addClass("subtle"),
input = $(document.createElement("input"))
.attr("type", "text")
.attr("name", "search")
.attr("placeholder", "e.g. Lauren Walker"),
hiddenInput = $(document.createElement("input"))
.attr("type", "hidden")
.attr("name", "subject")
.addClass("hidden"),
searchCell = $(document.createElement("td"))
.addClass("search")
.attr("colspan", "2")
.append(label, input, hiddenInput),
view = this;
//Setup the autocomplete widget for the input so users can search for people and groups
input.autocomplete({
source: function (request, response) {
var beforeRequest = function () {
//loadingSpinner.show();
};
var afterRequest = function () {
//loadingSpinner.hide();
};
return MetacatUI.appLookupModel.getAccountsAutocomplete(
request,
response,
beforeRequest,
afterRequest,
);
},
select: function (e, ui) {
e.preventDefault();
var value = ui.item.value;
hiddenInput.val(value);
input.val(value);
view.updateSubject();
},
position: {
my: "left top",
at: "left bottom",
of: input,
collision: "flip",
},
appendTo: searchCell,
minLength: 2,
});
this.$el.append(searchCell);
} else {
try {
if (this.$el.is(".new")) {
this.$el.removeClass("new");
}
//Create elements for the 'Name' column of this table row
var subject = this.model.get("subject"),
icon;
//If the subject is public, don't display an icon
if (subject == "public") {
icon = "";
}
//If this is a group subject, display the group icon
else if (this.model.isGroup()) {
icon = $(document.createElement("i")).addClass(
"icon icon-on-left icon-group",
);
}
//If this is a username, display the user icon
else {
icon = $(document.createElement("i")).addClass(
"icon icon-on-left icon-user",
);
}
//Get the user or group's name - or use the subject, as a backup
var name = this.model.get("name") || subject;
//Display "You" next to the user's own name, for extra helpfulness
if (subject == MetacatUI.appUserModel.get("username")) {
name += " (You)";
}
//Create an element for the name
var nameEl = $(document.createElement("span")).text(name);
this.$el.append(
$(document.createElement("td"))
.addClass("name")
.append(icon, nameEl),
);
} catch (e) {
console.error(
"Couldn't render the name column of the AccessRuleView: ",
e,
);
}
try {
//If this subject is an ORCID, display the ORCID and ORCID icon
if (subject.indexOf("orcid") >= 0) {
//Create the "subject/orcid" column
var orcidImg = $(document.createElement("img"))
.attr("src", MetacatUI.root + "/img/orcid_64x64.png")
.addClass("orcid icon icon-on-left"),
orcid = $(document.createElement("span")).text(
this.model.get("subject"),
);
this.$el.append(
$(document.createElement("td"))
.addClass("subject")
.append(orcidImg, orcid),
);
} else {
//For other subject types, don't show an ORCID icon
this.$el.append(
$(document.createElement("td"))
.addClass("subject")
.text(this.model.get("subject")),
);
}
} catch (e) {
console.error(
"Couldn't render the subject column of the AccessRuleView: ",
e,
);
}
}
try {
if (this.allowChanges) {
//Create the access/permission options select dropdown
var accessOptions = $(document.createElement("select"));
//Create option elements for each access rule type that is enabled in the app
_.mapObject(
MetacatUI.appModel.get("accessRuleOptions"),
function (isEnabled, optionType) {
if (isEnabled) {
var option = $(document.createElement("option"))
.attr("value", optionType)
.text(
MetacatUI.appModel.get("accessRuleOptionNames")[
optionType
],
);
//If this is the access type enabled in this AccessRule, then select this option
if (this.model.get(optionType)) {
option.prop("selected", "selected");
}
accessOptions.append(option);
}
},
this,
);
} else {
//Create an element to display the access type
var accessOptions = $(document.createElement("span"));
//Create option elements for each access rule type that is enabled in the app
_.mapObject(
MetacatUI.appModel.get("accessRuleOptions"),
function (isEnabled, optionType) {
//If this is the access type enabled in this AccessRule, then select this option
if (this.model.get(optionType)) {
accessOptions
.text(
MetacatUI.appModel.get("accessRuleOptionNames")[
optionType
],
)
.attr("title", "This cannot be changed.");
}
},
this,
);
}
//Create the table cell and add the access options element
this.$el.append(
$(document.createElement("td"))
.addClass("access")
.append(accessOptions),
);
} catch (e) {
console.error(
"Couldn't render the access column of the AccessRuleView: ",
e,
);
}
//Render the Remove column of the table
try {
if (this.isNew) {
var addIcon = $(document.createElement("i"))
.addClass("add icon icon-plus")
.attr("title", "Add this access");
//Create an empty table cell for "new" blank rows
this.$el.append(
$(document.createElement("td"))
.addClass("add-rule")
.append(addIcon),
);
} else {
//Only display a remove icon if we are allowing changes to this AccessRule
if (this.allowChanges) {
//Create a remove icon
var userType = this.model.isGroup() ? "group" : "person",
removeIcon = $(document.createElement("i"))
.addClass("remove icon icon-remove")
.attr("title", "Remove access for this " + userType);
//Create a table cell and append the remove icon
this.$el.append(
$(document.createElement("td"))
.addClass("remove-rule")
.append(removeIcon),
);
} else {
//Add an empty table cell so the other rows don't look weird, if they have remove icons
this.$el.append($(document.createElement("td")));
}
}
} catch (e) {
console.error(
"Couldn't render a remove button for an access rule: ",
e,
);
}
//If there is no name set on this model, listen to when it may be set, and update the view
if (!this.model.get("name")) {
this.listenToOnce(
this.model,
"change:name",
this.updateNameDisplay,
);
}
//Listen to changes on the access options and update the view if they are changed
this.listenTo(
this.model,
"change:read change:write change:changePermission",
this.updateAccessDisplay,
);
//When the model is removed from the collection, remove this view
this.listenTo(this.model, "remove", this.onRemove);
//Attach the AccessRule model to the view element
this.$el.data("model", this.model);
this.$el.data("view", this);
} catch (e) {
console.error(e);
//Don't display a message to the user since this view is pretty small. Just remove it from the page.
this.$el.remove();
}
},
/**
* Update the name in this view with the name from the model
*/
updateNameDisplay: function () {
//If there is no name set on the model, exit now, so that we don't show an empty string or falsey value
if (!this.model.get("name")) {
return;
}
var name = this.model.get("name");
//Display "You" next to the user's own name, for extra helpfulness
if (
this.model.get("subject") == MetacatUI.appUserModel.get("username")
) {
name += " (You)";
}
//Find the name element and update the text content
this.$(".name span").text(name);
},
/**
* Update the AccessRule model with the selected access option
*/
updateAccess: function () {
try {
//Get the value of the dropdown
var selection = this.$(".access select").val();
//If nothing was selected for some reason, exit now
if (!selection) {
return;
}
if (selection == "read") {
this.model.set("read", true);
this.model.set("write", null);
this.model.set("changePermission", null);
} else if (selection == "write") {
this.model.set("read", true);
this.model.set("write", true);
this.model.set("changePermission", null);
} else if (selection == "changePermission") {
this.model.set("read", true);
this.model.set("write", true);
this.model.set("changePermission", true);
}
} catch (e) {
console.error(e);
}
},
/**
* Update the access in this view with the access from the model
*/
updateAccessDisplay: function () {
//Get the select dropdown menu from this view
var select = this.$(".access select");
//Update the select dropdown menu with the value from the model
if (this.model.get("changePermission")) {
select.val("changePermission");
} else if (this.model.get("write")) {
select.val("write");
} else {
select.val("read");
}
},
/**
* Update the subject of the AccessRule
*/
updateSubject: function () {
//Get the subject from the hidden text input, which is populated from the
// jQueryUI autocomplete widget
var subject = this.$(".search input.hidden").val();
//If the hidden input doesn't have a value, get the value from the visible input
if (!subject) {
subject = this.$(".search input:not(.hidden)").val();
}
//If there is no subject typed in, exit
if (!subject) {
return;
}
//Set the subject on the model
this.model.set("subject", subject);
this.isNew = false;
this.render();
},
/**
* Updates the model associated with this view
*/
updateModel: function () {
//Update the access and the subject
this.updateAccess();
this.updateSubject();
},
/**
* Remove this AccessRule from the AccessPolicy
*/
onRemove: function () {
//If it is the rightsHolder of the object, don't remove the view
if (
this.model.get("dataONEObject") &&
this.model.get("dataONEObject").get("rightsHolder") ==
this.model.get("subject")
) {
return;
}
//Remove this view from the page
this.remove();
},
/**
* Handles when the user has typed at least one character in the name search input
* @param {Event} e - The keypress event
*/
listenForEnter: function (e) {
try {
if (!e) {
return;
}
//If Enter was pressed,
if (e.keyCode == 13) {
//Update the subject on this model
this.updateSubject();
}
} catch (e) {
MetacatUI.appView.showAlert(
"This group or person could not be added.",
"alert-error",
this.$el,
3000,
);
console.error(
"Error while listening to the Enter key in AccessRuleView: ",
e,
);
}
},
},
);
return AccessRuleView;
});