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
* @extends Backbone.Collection
*/
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;
});