Source: src/js/collections/UserGroup.js

/*global define */
define(['jquery', 'underscore', 'backbone', 'models/UserModel'],
	function($, _, Backbone, UserModel) {
	'use strict';

	/**
	 * @class UserGroup
	 * @classdesc The collection of Users that represent a DataONE group
   * @classcategory Collections
	 */
	var UserGroup = Backbone.Collection.extend(
    /** @lends UserGroup.prototype */{
		// Reference to this collection's model.
		model: UserModel,

		//Custom attributes of groups
		groupId: "",
		name: "",
		nameAvailable: null,

		url: function(){
			return MetacatUI.appModel.get("accountsUrl") + encodeURIComponent(this.groupId);
		},

		comparator: "lastName", //Sort by last name

		initialize: function(models, options) {
			if((typeof models == "undefined") || !models)
				var models = [];

			if(typeof options !== "undefined"){
				//Save our options
				$.extend(this, options);
				this.groupId = options.groupId || "";
				this.name    = options.name    || "";
				this.pending = (typeof options.pending === "undefined") ? false : options.pending;

				//If raw data is passed, parse it to get a list of users to be added to this group
				if(options.rawData){

					//Get a list of UserModel attributes to add to this collection
					var toAdd = this.parse(options.rawData);

					//Create a UserModel for each user
					_.each(toAdd, function(modelAttributes){
						//Don't pass the raw data to the UserModel creation because it is redundant-
						//We already parsed the raw data when we called add() above
						var rawDataSave = modelAttributes.rawData;
						modelAttributes.rawData = null;

						//Create the model then add the raw data back
						var member = new UserModel(modelAttributes);
						member.set("rawData", rawDataSave);

						models.push(member);
					});
				}
			}

			//Add all our models to this collection
			this.add(models);
		},

		/*
		 * Gets the group from the server. Options object uses the BackboneJS options API
		 */
		getGroup: function(options){
			if(!this.groupId && this.name){
				this.groupId = "CN=" + this.name + ",DC=dataone,DC=org";
			}

			this.fetch(options);

			return this;
		},

		/*
		 * Fetches the group info from the server. Should not be called directly - use getGroup() instead
		 */
		fetch: function (options) {
	        options = options || { silent: false, reset: false, remove: false };
	        options.dataType = "xml";
	        options.error = function(collection, response, options){
	        	//If this group is not found, then the name is available
	        	if((response.status == 404) && (response.responseText.indexOf("No Such Object") > -1)){
	        		collection.nameAvailable = true;
	        		collection.trigger("nameChecked", collection);
	        	}
	        }
	        return Backbone.Collection.prototype.fetch.call(this, options);
	    },

	    /*
	     * Backbone.js override - parses the XML reponse from DataONE and creates a JSON representation that will
	     * be used to create UserModels
	     */
		parse: function(response, options){
			if(!response) return;

			//This group name is not available/already taken
			this.nameAvailable = false;
			this.trigger("nameChecked", this);

			var group = $(response).find("group subject:contains('" + this.groupId + "')").parent("group"),
				people = $(response).find("person"),
				collection = this,
				toAdd = new Array(),
				existing = this.pluck("username");

			if(!people.length)
				people = $(group).find("hasMember");

			//Make all existing usernames lowercase for string matching
			if(existing.length) existing = _.invoke(existing, "toLowerCase");

			this.name = $(group).children("groupName").text();

			_.each(people, function(person){

				//The tag name is "hasMember" if we retrieved info about this group from the group nodes only
				if(person.tagName == "hasMember"){
					var username = $(person).text();

					//If this user is already in the group, skip adding it
					if(_.contains(existing, username.toLowerCase())) return;

					var user = new UserModel({ username: username }),
						userAttr = user.toJSON();

					toAdd.push(userAttr);
				}
				//The tag name is "person" if we retrieved info about this group through the /accounts service, which includes all nodes about all members
				else{
					//If this user is not listed as a member of this group, skip it
					if($(person).children("isMemberOf:contains('" + collection.groupId + "')").length < 1)
						return;

					//Username of this person
					var username = $(person).children("subject").text();

					//If this user is already in the group, skip adding it
					if(_.contains(existing, username.toLowerCase())) return;

					//User attributes - pass the full response for the UserModel to parse
					var userAttr = new UserModel({username: username}).parseXML(response);

					//Add to collection
					toAdd.push(userAttr);
				}
			});

			return toAdd;
		},

		/*
		 * An alternative to Backbone sync
		 * - will send a POST request to DataONE CNIdentity.createGroup() to create this collection as a new DataONE group
		 * or
		 * - will send a PUT request to DataONE CNIdentity.updateGroup() to update this existing DataONE group
		 *
		 *  If this group is marked as pending, then the group is created, otherwise it's updated
		 */
		save: function(onSuccess, onError){
			if(this.pending && (this.nameAvailable == false)) return false;

			var memberXML = "",
				ownerXML = "",
				collection = this;

			//Create the member and owner XML
			this.forEach(function(member){
				//Don't list yourself as an owner or member (implied)
				if(MetacatUI.appUserModel == member) return;

				var username = member.get("username") ? member.get("username").trim() : null;
				if(!username) return;

				memberXML += "<hasMember>" + username + "</hasMember>";

				if(collection.isOwner(member))
					ownerXML += "<rightsHolder>" + username + "</rightsHolder>";
			});

			//Create the group XML
			var groupXML =
				'<?xml version="1.0" encoding="UTF-8"?>'
				+ '<d1:group xmlns:d1="http://ns.dataone.org/service/types/v1">'
					+ '<subject>'   + this.groupId + '</subject>'
					+ '<groupName>' + this.name    + '</groupName>'
					+ memberXML
					+ ownerXML
				+ '</d1:group>';

			var xmlBlob = new Blob([groupXML], {type : 'application/xml'});
			var formData = new FormData();
			formData.append("group", xmlBlob, "group");

			// AJAX call to update
			$.ajax({
				type: this.pending? "POST" : "PUT",
				cache: false,
			    contentType: false,
			    processData: false,
				xhrFields: {
					withCredentials: true
				},
				headers: {
			        "Authorization": "Bearer " + MetacatUI.appUserModel.get("token")
			    },
				url: MetacatUI.appModel.get("groupsUrl"),
				data: formData,
				success: function(data, textStatus, xhr) {
					if(typeof onSuccess != "undefined")
						onSuccess(data);

					collection.pending = false;
					collection.nameAvailable = null;
					collection.getGroup();
				},
				error: function(xhr, textStatus, error) {
					if(typeof onError != "undefined")
						onError(xhr);
				}
			});

			return true;
		},

		/*
		 * For pending groups only (those in the creation stage)
		 * Will check if the given name/id is available
		 */
		checkName: function(name){
			//Only check the name for pending groups
			if(!this.pending) return;

			//Reset the name and ID
			this.name = name || this.name;
			this.groupId = null;
			this.nameAvailable = null;

			//Get group info/check name availablity
			this.getGroup({ add: false });
		},

		/*
		 * Retrieves the UserModels that are rightsHolders of this group
		 */
		getOwners: function(){
			var groupId = this.groupId;
			return _.filter(this.models, function(user){
				return _.contains(user.get("isOwnerOf"), groupId);
			});
		},

		/*
		 * Shortcut function - will check if a specified User is an owner of this group
		 */
		isOwner: function(model){
			if(typeof model === "undefined") return false;

			if(this.pending && (model == MetacatUI.appUserModel)) return true;

			var usernames = [];
			_.each(this.getOwners(), function(user){ usernames.push(user.get("username")); });

			return _.contains(usernames, model.get("username"));
		}

	});

	return UserGroup;

});