Skip to content

Commit

Permalink
Support multiple role selection in projects
Browse files Browse the repository at this point in the history
Horizon currently overwrites existing roles when saving project
membership. The backend was only initialising the form with the
first role of each user, so when the form was submitted any other
roles on *all* users were removed.

Changes:
- Fix backend to pass through all roles
- Keep role dropdown open on click, clicks add a tick to a role
- Show the first two roles on the dropdown label, add a "..." for
   three or more.

Fixes bug #1081374.

Change-Id: Iaf64afc4c50d4d24d6acb529a6e810a0ba154505
  • Loading branch information
spearki committed Mar 3, 2013
1 parent db410ce commit 4c34f5f
Show file tree
Hide file tree
Showing 4 changed files with 89 additions and 40 deletions.
110 changes: 75 additions & 35 deletions horizon/static/horizon/js/horizon.projects.js
Expand Up @@ -19,11 +19,20 @@ horizon.projects = {

/*
* Gets the html select element associated with a given
* role id for role_id.
* role id.
**/
get_role_element: function(role_id) {
return $('select[id^="id_role_' + role_id + '"]');
},

/*
* Gets the html ul element associated with a given
* user id. I.e., the user's row.
**/
get_user_element: function(user_id) {
return $('li[data-user-id$=' + user_id + ']').parent();
},

/*
* Gets the html select element associated with a given
Expand Down Expand Up @@ -115,16 +124,16 @@ horizon.projects = {
});
},
/*
* Checks to see whether a user is a member of the current project.
* If they are, returns the id of their primary role.
* Returns the ids of roles the user is a member of.
**/
is_project_member: function(user_id) {
get_user_roles: function(user_id) {
var roles = [];
for (var role in horizon.projects.current_membership) {
if ($.inArray(user_id, horizon.projects.current_membership[role]) >= 0) {
return role;
roles.push(role);
}
}
return false;
return roles;
},

/*
Expand All @@ -133,8 +142,6 @@ horizon.projects = {
**/
update_role_lists: function(role_id, new_list) {
this.get_role_element(role_id).val(new_list);
this.get_role_element(role_id).find("option[value='" + role_id + "").attr("selected", "selected");

horizon.projects.current_membership[role_id] = new_list;
},

Expand Down Expand Up @@ -178,11 +185,45 @@ horizon.projects = {
horizon.projects.update_role_lists(role_id, role_list);
},

update_user_role_dropdown: function(user_id, role_ids, user_el) {
if (typeof(role_ids) === 'undefined') {
role_ids = horizon.projects.get_user_roles(user_id);
}
if (typeof(user_el) === 'undefined') {
user_el = horizon.projects.get_user_element(user_id);
}

var $dropdown = user_el.find("li.member").siblings('.dropdown');
var $role_items = $dropdown.children('.role_dropdown').children('li');

$role_items.each(function (idx, el) {
if (_.contains(role_ids, $(el).data('role-id'))) {
$(el).addClass('selected');
} else {
$(el).removeClass('selected');
}
});

// set the selection back to default role
var $roles_display = $dropdown.children('.dropdown-toggle').children('.roles_display');
var roles_to_display = [];
for (var i = 0; i < role_ids.length; i++) {
if (i == 2) {
roles_to_display.push('...');
break;
}
roles_to_display.push(horizon.projects.roles[role_ids[i]]);
}
text = roles_to_display.join(', ');
if (text.length == 0) text = 'No roles';
$roles_display.text(text);
},

/*
* Generates the HTML structure for a user that will be displayed
* as a list item in the project member list.
**/
generate_user_element: function(user_name, user_id, text) {
generate_user_element: function(user_name, user_id, role_ids, text) {
var str_id = "id_user_" + user_id;

var roles = [];
Expand All @@ -200,6 +241,7 @@ horizon.projects = {
text: text,
roles: roles},
user_el = $(template.render(params));
this.update_user_role_dropdown(str_id, role_ids, user_el);
return $(user_el);
},

Expand All @@ -213,11 +255,6 @@ horizon.projects = {
return $li;
},

set_selected_role: function(selected_el, role_id) {
$(selected_el).text(horizon.projects.roles[role_id]);
$(selected_el).attr('data-role-id', role_id);
},

/*
* Generates the HTML structure for the project membership UI.
**/
Expand All @@ -226,14 +263,12 @@ horizon.projects = {
for (user in horizon.projects.users) {
var user_id = user;
var user_name = horizon.projects.users[user];
var role_id = this.is_project_member(user_id);
if (role_id) {
$(".project_members").append(this.generate_user_element(user_name, user_id, "-"));
var $selected_role = $("li[data-user-id$='" + user_id + "']").siblings('.dropdown').children('.dropdown-toggle').children('span');
horizon.projects.set_selected_role($selected_role, role_id);
var role_ids = this.get_user_roles(user_id);
if (role_ids.length > 0) {
$(".project_members").append(this.generate_user_element(user_name, user_id, role_ids, "-"));
}
else {
$(".available_users").append(this.generate_user_element(user_name, user_id, "+"));
$(".available_users").append(this.generate_user_element(user_name, user_id, role_ids, "+"));
}
}
horizon.projects.detect_no_results();
Expand Down Expand Up @@ -313,26 +348,24 @@ horizon.projects = {
evt.preventDefault();
var available = $(".available_users").has($(this)).length;
var user_id = horizon.projects.get_field_id($(this).parent().siblings().attr('data-user-id'));
var user_el = $(this).parent().parent();

if (available) {
$(this).text("-");
$(".project_members").append(user_el);

if (horizon.projects.has_roles) {
var default_role = horizon.projects.default_role_id;
$(this).parent().siblings(".role_options").show();
horizon.projects.add_user_to_role(user_id, default_role);
horizon.projects.update_user_role_dropdown(user_id, [default_role], user_el);
}
$(".project_members").append($(this).parent().parent());

horizon.projects.add_user_to_role(user_id, horizon.projects.default_role_id);
}
else {
$(this).text("+");
$(this).parent().siblings(".role_options").hide();
$(".available_users").append($(this).parent().parent());

$(".available_users").append(user_el);
horizon.projects.remove_user_from_role(user_id);

// set the selection back to default role
var $selected_role = $(this).parent().siblings('.dropdown').children('.dropdown-toggle').children('.selected_role');
horizon.projects.set_selected_role($selected_role, horizon.projects.default_role_id);
}

// update lists
Expand Down Expand Up @@ -368,17 +401,23 @@ horizon.projects = {
**/
select_member_role: function() {
$(".available_users, .project_members").on('click', '.role_dropdown li', function (evt) {
var $selected_el = $(this).parent().prev().children('.selected_role');
$selected_el.text($(this).text());
evt.preventDefault();
evt.stopPropagation();

// get the newly selected role and the member's name
var new_role_id = $(this).attr("data-role-id");
var id_str = $(this).parent().parent().siblings(".member").attr("data-user-id");
var user_id = horizon.projects.get_field_id(id_str);

// update role lists
horizon.projects.remove_user_from_role(user_id, $selected_el.attr('data-role-id'));
horizon.projects.add_user_to_role(user_id, new_role_id);
if ($(this).hasClass('selected')) {
$(this).removeClass('selected');
horizon.projects.remove_user_from_role(user_id, new_role_id);
} else {
$(this).addClass('selected');
horizon.projects.add_user_to_role(user_id, new_role_id);
}
horizon.projects.update_user_role_dropdown(user_id);
});
},

Expand All @@ -390,12 +429,13 @@ horizon.projects = {
// add the user to the visible list
var user_name = $(this).find("option").text();
var user_id = $(this).find("option").attr("value");
$(".project_members").append(horizon.projects.generate_user_element(user_name, user_id, "-"));
var default_role_id = horizon.projects.default_role_id;
$(".project_members").append(horizon.projects.generate_user_element(user_name, user_id, [default_role_id], "-"));

// add the user to the hidden role lists and the users list
horizon.projects.users[user_id] = user_name;
$("select[multiple='multiple']").append("<option value='" + user_id + "'>" + horizon.projects.users[user_id] + "</option>");
horizon.projects.add_user_to_role(user_id, horizon.projects.default_role_id);
horizon.projects.add_user_to_role(user_id, default_role_id);

// remove option from hidden select
$(this).text("");
Expand Down
4 changes: 2 additions & 2 deletions horizon/templates/horizon/client_side/_project_user.html
Expand Up @@ -12,12 +12,12 @@
<li class="active"><a class="btn btn-primary" href="#add_remove">[[text]]</a></li>
<li class="dropdown role_options">
<a class="dropdown-toggle" data-toggle="dropdown" href="#">
<span class="selected_role">[[default_role]]</span>
<span class="roles_display">Roles</span>
<b class="caret"></b>
</a>
<ul class="dropdown-menu role_dropdown clearfix">
[[#roles]]
<li data-role-id="[[role_id]]">[[role_name]]</li>
<li data-role-id="[[role_id]]"><i class="icon-ok"></i> [[role_name]]</li>
[[/roles]]
</ul>
</li>
Expand Down
5 changes: 2 additions & 3 deletions openstack_dashboard/dashboards/admin/projects/workflows.py
Expand Up @@ -166,9 +166,8 @@ def __init__(self, request, *args, **kwargs):
exceptions.handle(request,
err_msg,
redirect=reverse(INDEX_URL))
if roles:
primary_role = roles[0].id
self.fields["role_" + primary_role].initial.append(user.id)
for role in roles:
self.fields["role_" + role.id].initial.append(user.id)

class Meta:
name = _("Project Members")
Expand Down
10 changes: 10 additions & 0 deletions openstack_dashboard/static/dashboard/less/horizon.less
Expand Up @@ -1453,10 +1453,20 @@ label.log-length {
-moz-box-shadow: none;
box-shadow: none;
z-index: 99999;
i {
opacity: 0;
}

&:hover {
background-color: #CDCDCD;
}
&.selected i {
opacity: 1;
}
}
.dropdown-menu.role_dropdown {
right: 0;
left: auto;
}
.nav .role_options {
float: right;
Expand Down

0 comments on commit 4c34f5f

Please sign in to comment.