Issue #1167 : Member Manager Fixes and implementation

* Implement the member research
 * Visual fix for the "add member" form
 * Use of the local storage to import all user (for the add member form)
 * Change the feedback of add and remove members
 * Add load state for the adding and removing users
 * Synchronize member number displays
 * Limit the number of members displayed
This commit is contained in:
Zacharie
2020-05-11 15:55:56 +02:00
committed by plegall
parent 14c47f0dd3
commit 1d3c08616b
4 changed files with 204 additions and 145 deletions
+1
View File
@@ -386,6 +386,7 @@ $template->assign(
'F_ADD_ACTION' => get_root_url().'admin.php?page=group_list',
'U_HELP' => get_root_url().'admin/popuphelp.php?page=group_list',
'PWG_TOKEN' => get_pwg_token(),
'CACHE_KEYS' => get_admin_client_cache_keys(array('groups', 'users')),
)
);
+184 -105
View File
@@ -217,7 +217,6 @@ var setupGroupBox = function (groupBox) {
});
groupBox.find(".group-rename form").on("submit", function (e) {
console.log("submit");
e.preventDefault();
if (groupBox.find(".group_name-editable").val() != groupBox.find("#group_name").html())
renameGroup(id, groupBox.find(".group_name-editable").val())
@@ -386,7 +385,7 @@ $(function () {
$(".not-in-selection-mode").hide();
$(".GroupManagerButtons").removeClass("visible");
} else {
$(".in-selection-mode").fadeOut();
$(".in-selection-mode").hide();
$(".not-in-selection-mode").removeAttr("style");
$(".Group-checkbox input").attr("checked", false);
$(".Group-checkbox input[type='checkbox']").trigger("change");
@@ -416,8 +415,6 @@ var updateSelectionPanel = function (changedState = "") {
updateStatePanel(changedState)
}
console.log(state);
$(".number-Selected").html(numSelect + "");
};
@@ -500,57 +497,51 @@ $('.ConfirmDeleteButton').on("click", function() {
// Initialize the research user bar
var selectize;
// List of users
var usersSearch = [];
// Initialize the cache
var usersCache = {};
var usersInGroup = [];
// Max offset of the user container (322 = 6 lines)
var maxOffsetUserCont = 322;
var infoUsers = $("<div class='ValidationUserAssociated'>"
+ "<p class='icon-ok'></p>"
+ "</div>")
// Setup the user research bar
$(function() {
// initialize the Selectize control
$select = $('.AddUserBlock input').selectize({
delimiter: ',',
persist: false,
plugins: ['remove_button']
});
$select = $('.AddUserBlock select').selectize({});
// fetch the instance
selectize = $select[0].selectize;
var idSearch = "";
$('.UserSearch input').on("focus", function() {
// Import users if it is not already done
if (usersSearch.length == 0) {
jQuery.ajax({
url: "ws.php?format=json&method=pwg.users.getList",
type: "POST",
data: "",
success: function (raw_data) {
data = jQuery.parseJSON(raw_data);
if (data.stat === "ok") {
usersSearch = data.result.users;
updateUserSearch();
selectize.refreshOptions();
}
}
});
} else if (idSearch != $("#UserList").attr("data-group_id")) {
if (idSearch != $("#UserList").attr("data-group_id")) {
updateUserSearch();
}
});
// Update User search bar (remove group users in selection)
updateUserSearch = function () {
selectize.clear();
if (usersCache = {}) {
usersCache = new UsersCache({
serverKey: serverKey,
serverId: serverId,
rootUrl: rootUrl
});
}
JSON.parse(usersCache.storage[usersCache.key]).data.forEach(function(u){
selectize.addOption({value:u.id, text:u.username})
})
idSearch = $("#UserList").attr("data-group_id");
selectize.clearOptions();
usersSearch.forEach(function(u){
isInGroup = false;
$('.UsernameBlock').each(function(){
if ($(this).data("id")==u.id)
isInGroup = true;
})
if (!isInGroup) {
selectize.addOption({value:u.id, text:u.username})
}
$('.UsernameBlock').each(function(){
selectize.removeOption($(this).data("id"));
})
}
});
@@ -569,18 +560,35 @@ var openUserManager = function(grp_id) {
loadState.reverse();
data = jQuery.parseJSON(raw_data);
if (data.stat === "ok") {
//Fill with user blocks
let users = data.result.users;
$(".UsersInGroupList").html('');
$(".UserNumberBadge").html(''+users.length);
users.forEach(u => {
addUserDisplay(u.username, u.id, grp_id);
});
$(".group-name-block p").html(
$("#group-" + grp_id + " #group_name").html() + " / " + str_user_list
)
$(".UsersInGroupList").html("");
//Display the popin
$('#UserList').fadeIn();
//Fill with user blocks
usersInGroup = data.result.users;
// Sort in alphabetic order
usersInGroup.sort(function( a, b ) {
if ( a.username.toLowerCase() < b.username.toLowerCase() ){
return 1;
} else return -1
});
let i = 0;
while ($(".UsersInGroupList").outerHeight() <= maxOffsetUserCont && usersInGroup[i] != undefined){
getUserDisplay(usersInGroup[i].username, usersInGroup[i].id, grp_id).prependTo(".UsersInGroupList");
i++;
};
while ($(".UsersInGroupList").height() > maxOffsetUserCont) {
$(".UsernameBlock").last().remove();
}
updateMembernumber(usersInGroup.length, grp_id);
//Attribute the group id to the div
$("#UserList").attr("data-group_id", grp_id);
//Clear the selection
selectize.clear();
$(".LinkUserManager a").attr("href","admin.php?page=user_list&group="+grp_id)
}
},
error: function (err) {
@@ -590,7 +598,7 @@ var openUserManager = function(grp_id) {
}
//Add a user block
var addUserDisplay = function(username, user_id, grp_id) {
var getUserDisplay = function(username, user_id, grp_id) {
let userBlock = $('<div class="UsernameBlock" data-id='+user_id+'>'+
'<span class="icon-user-1"></span>'+
'<p>'+username+'</p>'+
@@ -598,12 +606,18 @@ var addUserDisplay = function(username, user_id, grp_id) {
'<span class="icon-cancel"></span>'+
'<p class="TooltipText">Dissociate user from this group</p>'+
'</div>'+
'<div class="UserInfo"><p>User Dissociated</p></div>'+
'</div>');
userBlock.appendTo(".UsersInGroupList");
//Setup the delete action
userBlock.find(".icon-cancel").on("click", function () {
while ($(".UsersInGroupList")[0].offsetHeight > maxOffsetUserCont) {
$(".UsernameBlock").last().remove();
}
//Setup the delete action
userBlock.find(".icon-cancel").on("click", function () {
userBlock.find(".icon-cancel").addClass("icon-spin6")
userBlock.find(".icon-cancel").addClass("animate-spin")
userBlock.find(".icon-cancel").css("pointer-events", "none")
userBlock.find(".icon-cancel").removeClass("icon-cancel")
jQuery.ajax({
url: "ws.php?format=json&method=pwg.groups.deleteUser",
type: "POST",
@@ -611,21 +625,23 @@ var addUserDisplay = function(username, user_id, grp_id) {
success: function (raw_data) {
data = jQuery.parseJSON(raw_data);
if (data.stat === "ok") {
//Setup User Info
userBlock.find(".UserInfo")
.css("display", "flex")
.addClass("UserInfo-dissociated").removeClass("UserInfo-associated")
.hide()
.find("p").html(str_user_dissociated);
userBlock.find(".UserInfo").fadeIn();
userBlock.delay(1000).fadeOut(function(){
userBlock.remove();
updateUserSearch();
//Update member number
$(".GroupContainer[data-id="+grp_id+"] .group_number_users")
.html(($(".UsernameBlock").length) + " " + str_member_default);
$(".UserNumberBadge").html(''+$(".UsernameBlock").length);
})
infoUsers.remove();
infoUsers.insertAfter(userBlock).hide();
infoUsers.find("p").html(str_user_dissociated);
infoUsers.fadeIn()
userBlock.remove()
updateUserSearch();
while ($(".UsersInGroupList").height() > maxOffsetUserCont) {
$(".UsernameBlock").last().remove();
}
usersInGroup = usersInGroup.filter(u => u.id != user_id)
//Update member number
updateMembernumber(parseInt($(".UserNumberBadge").html()) -1, grp_id);
}
}
});
@@ -633,53 +649,101 @@ var addUserDisplay = function(username, user_id, grp_id) {
return userBlock;
}
//Update member number function
function updateMembernumber(number, grp_id) {
$(".GroupContainer[data-id="+grp_id+"] .group_number_users")
.html(number + " " + str_member_default);
$(".UserNumberBadge").html(number);
$(".AmountOfUsersShown strong:nth-child(2)").html(number)
$(".AmountOfUsersShown strong:nth-child(1)").html($(".UsernameBlock").length)
}
// Close pop-up on cross click
$(".CloseUserList").on("click", function() {$('#UserList').fadeOut();})
// Adding Group Action
$(".AddUserBlock button").on("click", function () {
let grp_id = $("#UserList").attr("data-group_id")
let grp_id = $("#UserList").attr("data-group_id");
let usersString = ""
// Get selected ids
let ids = selectize.getValue();
ids.split(',').forEach(function(id){
usersString += "&user_id[]="+id
});
jQuery.ajax({
url: "ws.php?format=json&method=pwg.groups.addUser",
type: "POST",
data: "group_id=" + grp_id+ usersString + "&pwg_token=" + pwg_token,
success: function (raw_data) {
data = jQuery.parseJSON(raw_data);
ids.split(',').forEach(function(id){
// Get the username
let username = "undefined";
usersSearch.forEach(function(u) {
if (u.id == id) {
username = u.username;
let id = selectize.getValue();
if (id != "") {
let loadState = new TemporaryState();
loadState.changeHTML($("#UserSubmit"),"<i class='icon-spin6 animate-spin'> </i>");
loadState.removeClass($("#UserSubmit"),"icon-user-add");
loadState.changeAttribute($("#UserSubmit"),"css","pointer-events:none")
jQuery.ajax({
url: "ws.php?format=json&method=pwg.groups.addUser",
type: "POST",
data: "group_id=" + grp_id+ "&user_id=" + id + "&pwg_token=" + pwg_token,
success: function (raw_data) {
loadState.reverse()
data = jQuery.parseJSON(raw_data);
if (data.stat === "ok") {
// Get the username
let username = "undefined";
JSON.parse(usersCache.storage[usersCache.key]).data.forEach(function(u) {
if (u.id == id) {
username = u.username;
}
})
let userBlock = getUserDisplay(username, id, grp_id).prependTo(".UsersInGroupList");
infoUsers.remove();
infoUsers.insertAfter(userBlock).hide();
infoUsers.find("p").html(str_user_associated);
infoUsers.fadeIn()
updateUserSearch();
usersInGroup.push({username: username, id:id});
while ($(".UsersInGroupList").height() > maxOffsetUserCont) {
$(".UsernameBlock").last().remove();
}
})
let userBlock = addUserDisplay(username, id, grp_id)
//Setup User Info
userBlock.find(".UserInfo")
.css("display", "flex")
.addClass("UserInfo-associated").removeClass("UserInfo-dissociated")
.show()
.find("p").html(str_user_associated);
userBlock.delay(1000).fadeIn();
userBlock.find(".UserInfo").delay(1000).fadeOut();
updateUserSearch();
//Update member number
$(".GroupContainer[data-id="+grp_id+"] .group_number_users")
.html(($(".UsernameBlock").length) + " " + str_member_default);
$(".UserNumberBadge").html(''+$(".UsernameBlock").length);
})
}
});
//Update member number
updateMembernumber(parseInt($(".UserNumberBadge").html()) + 1, grp_id);
}
}
});
}
});
$(".input-user-name").on("input", function() {
searchString = $(this).val().toLowerCase();
grp_id = $(".UserListPopIn").data("group_id");
if (searchString != "") {
infoUsers.remove();
$(".UsersInGroupListContainer").css("min-height", $(".UsersInGroupListContainer").height())
usersInGroup.forEach(function(u) {
let isSearched = u.username.toLowerCase().includes(searchString)
if ($(".UsernameBlock[data-id="+u.id+"]").length != 0) {
if (!isSearched) {
$(".UsernameBlock[data-id="+u.id+"]").remove();
}
} else if (isSearched) {
getUserDisplay(u.username, u.id, grp_id)
.prependTo(".UsersInGroupList");
}
})
} else {
$(".UsersInGroupListContainer").css("min-height", "")
$(".UsersInGroupList").html("");
let i = 0;
while ($(".UsersInGroupList").outerHeight() <= maxOffsetUserCont && usersInGroup[i] != undefined){
getUserDisplay(usersInGroup[i].username, usersInGroup[i].id, grp_id)
.prependTo(".UsersInGroupList");
i++;
}
}
while ($(".UsersInGroupList").height() > maxOffsetUserCont) {
$(".UsernameBlock").last().remove();
}
})
// Class to implement a temporary state and reverse it
class TemporaryState {
//Arrays to reverse changes
@@ -688,8 +752,8 @@ class TemporaryState {
htmlChanges = []; //Html changes : {object(s), (old) html}
/**
* Change an attribute of an object
* @param {HTML Node} obj HTML Object(s)
* Change temporaly an attribute of an object
* @param {Jquery Object(s)} obj HTML Object(s)
* @param {String} attr Attribute
* @param {String} tempVal Temporary value of the attribute
*/
@@ -706,7 +770,7 @@ class TemporaryState {
/**
* Add/remove a class temporarily
* @param {HTML Node} obj HTML Object
* @param {Jquery Object(s)} obj HTML Object
* @param {Boolean} st Add (true) or Remove (false) the class
* @param {String} loadclass Class Name
*/
@@ -726,14 +790,29 @@ class TemporaryState {
}
}
/**
* Add temporarily a class to the object
* @param {Jquery Object(s)} obj
* @param {string} tempclass
*/
addClass(obj, tempclass) {
this.changeClass(obj, true, tempclass);
}
/**
* Remove temporarily a class to the object
* @param {Jquery Object(s)} obj
* @param {string} tempclass
*/
removeClass(obj, tempclass) {
this.changeClass(obj, false, tempclass);
}
/**
* Change temporaly the html of objects (remove event handlers on the actual content)
* @param {Jquery Object(s)} obj
* @param {string} temphtml
*/
changeHTML(obj, temphtml) {
for (let i = 0; i < obj.length; i++) {
this.htmlChanges.push({
+12 -8
View File
@@ -15,11 +15,18 @@ var str_yes_delete_confirmation = "{'Yes, delete it'|@translate}"
var str_no_delete_confirmation = "{"No, I've changed my mind"|@translate}"
var str_user_associated = "{"User Associated"|@translate}"
var str_user_dissociated = "{"User Dissociated"|@translate}"
var str_user_list = "{"User List"|@translate}"
var serverKey = '{$CACHE_KEYS.users}'
var serverId = '{$CACHE_KEYS._hash}'
var rootUrl = '{$ROOT_URL}'
{/footer_script}
{combine_script id='jquery.selectize' load='footer' path='themes/default/js/plugins/selectize.min.js'}
{combine_css path="themes/default/js/plugins/selectize.{$themeconf.colorscheme}.css"}
{combine_script id='LocalStorageCache' load='footer' path='admin/themes/default/js/LocalStorageCache.js'}
{combine_script id='jquery.confirm' load='footer' require='jquery' path='themes/default/js/plugins/jquery-confirm.min.js'}
{combine_css path="themes/default/js/plugins/jquery-confirm.min.css"}
{combine_css path="admin/themes/default/fontello/css/animation.css"}
@@ -42,7 +49,7 @@ var str_user_dissociated = "{"User Dissociated"|@translate}"
<div class="groupError icon-cancel"></div>
</div>
<div class="icon-star is-default-token{if !$grp_is_default} deactivate{/if}" ></div>
<div class="icon-star not-in-selection-mode is-default-token{if !$grp_is_default} deactivate{/if}" ></div>
<div class="icon-ellipsis-vert group-dropdown-options not-in-selection-mode">
<div id="GroupOptions">
@@ -77,7 +84,7 @@ var str_user_dissociated = "{"User Dissociated"|@translate}"
</div>
<a id="UserListTrigger" class="icon-user-1 manage-users not-in-selection-mode GroupManagerButtons">Manage users</a>
<a class="icon-lock manage-permissions not-in-selection-mode GroupManagerButtons">Manage permissions</a>
<a class="icon-lock manage-permissions not-in-selection-mode GroupManagerButtons" href="admin.php?page=group_perm&group_id={$grp_id}">Manage permissions</a>
</div>
{/function}
{/function}
@@ -191,15 +198,15 @@ var str_user_dissociated = "{"User Dissociated"|@translate}"
<div class="group-name-block">
<span class="icon-list-bullet"></span>
<p id="group_name" contenteditable="true">{$group.NAME} / User List</p>
<p></p>
<span class="UserNumberBadge">25</span>
</div>
<div class="UserListAddFilterUsers">
<div class="AddUserBlock">
<p>Associate User</p>
<input type=text class="UserSearch" type="text" name="username" maxlength="50" size="20" placeholder="John Doe"></select>
<button class="icon-ok submit" name="submit_add" id="UserSubmit" type="submit">Select user(s)</button>
<select class="UserSearch" placeholder="John Doe"></select>
<button class="icon-user-add submit" name="submit_add" id="UserSubmit" type="submit"></button>
</div>
<div class="FilterUserBlock">
<div class="AmountOfUsersShown">
@@ -215,9 +222,6 @@ var str_user_dissociated = "{"User Dissociated"|@translate}"
<div class="UsersInGroupList row">
<div class='ValidationUserAssociated'>
<p class="icon-ok">User Associated</p>
</div>
</div>
</div>
+7 -32
View File
@@ -2244,7 +2244,6 @@ input:checked + .slider:before {
.UserListPopIn{
position: fixed;
z-index: 100;
padding-top: 100px;
left: 0;
top: 0;
width: 100%;
@@ -2258,7 +2257,8 @@ input:checked + .slider:before {
width:1100px;
position:absolute;
left:50%;
transform:translateX(-50%);
top: 50%;
transform:translate(-50%, -48%);
text-align:left;
padding:30px;
}
@@ -2316,13 +2316,13 @@ input:checked + .slider:before {
}
#UserList .AddUserBlock p,
#UserList .AddUserBlock input,
#UserList .AddUserBlock select,
#UserList .AddUserBlock button{
display:inline-block;
margin:0 5px;
}
#UserList .AddUserBlock input,
#UserList .AddUserBlock select,
#UserList .FilterUserBlock input{
padding:11px 10px;
}
@@ -2367,30 +2367,6 @@ input:checked + .slider:before {
border-radius:25px;
}
.UsernameBlock .UserInfo {
display: none;
position: absolute;
top: 0;
width: 100%;
left: 0px;
height: 100%;
border-radius: 25px;
}
.UsernameBlock .UserInfo-dissociated {
color: #f22;
background-color: #ffd5dc;
}
.UsernameBlock .UserInfo-associated {
color: #0a0;
background-color:#c2f5c2;
}
.UsernameBlock .UserInfo p {
margin: auto
}
.UsersInGroupListContainer{
max-height:315px;
@@ -2424,7 +2400,7 @@ input:checked + .slider:before {
background-color:#f3f3f3;
color: #3c3c3c;
text-align: left;
padding: 2px 10px;
padding: 5px 10px;
border-radius: 10px;
border:1px solid #3c3c3c;
font-weight:700;
@@ -2432,7 +2408,7 @@ input:checked + .slider:before {
position: absolute;
z-index: 4;
top:10px;
left:10px;
left:20px;
transition: opacity 0.3s;
z-index:101;
}
@@ -2444,7 +2420,6 @@ input:checked + .slider:before {
.ValidationUserAssociated{
display:inline-block;
font-size:12px;
width:110px;
text-align:left;
padding:2px;
border-radius:10px;
@@ -2469,7 +2444,7 @@ input:checked + .slider:before {
.LinkUserManager{
text-align:center;
font-weight:700;
padding:30px 0;
padding-top:20px;
}
/********/