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

});