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 */ 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; });