Source: src/js/views/AccessRuleView.js

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