Add user editing support to user view model

This commit is contained in:
Bartek Fabiszewski 2019-12-29 13:33:05 +01:00
parent c04a45f8d3
commit 069eab1f63
7 changed files with 169 additions and 14 deletions

View File

@ -356,7 +356,7 @@ button > * {
border: 1px solid #888; border: 1px solid #888;
} }
#user-menu.menu-hidden { #user-menu.menu-hidden, a.menu-hidden {
display: none; display: none;
} }

View File

@ -91,7 +91,9 @@
*/ */
private static function exitWithStatus($isError, $extra = NULL) { private static function exitWithStatus($isError, $extra = NULL) {
$output = []; $output = [];
$output["error"] = $isError; if ($isError) {
$output["error"] = true;
}
if (!empty($extra)) { if (!empty($extra)) {
foreach ($extra as $key => $value) { foreach ($extra as $key => $value) {
$output[$key] = $value; $output[$key] = $value;

View File

@ -61,7 +61,7 @@
<div> <div>
<a data-bind="onShowUserMenu"><img class="icon" alt="<?= $lang['user'] ?>" src="images/user.svg"> <?= htmlspecialchars($auth->user->login) ?></a> <a data-bind="onShowUserMenu"><img class="icon" alt="<?= $lang['user'] ?>" src="images/user.svg"> <?= htmlspecialchars($auth->user->login) ?></a>
<div id="user-menu" class="menu-hidden"> <div id="user-menu" class="menu-hidden">
<a id="user-pass"><img class="icon" alt="<?= $lang['changepass'] ?>" src="images/lock.svg"> <?= $lang['changepass'] ?></a> <a id="user-pass" data-bind="onPasswordChange"><img class="icon" alt="<?= $lang['changepass'] ?>" src="images/lock.svg"> <?= $lang['changepass'] ?></a>
<a href="utils/logout.php"><img class="icon" alt="<?= $lang['logout'] ?>" src="images/poweroff.svg"> <?= $lang['logout'] ?></a> <a href="utils/logout.php"><img class="icon" alt="<?= $lang['logout'] ?>" src="images/poweroff.svg"> <?= $lang['logout'] ?></a>
</div> </div>
</div> </div>
@ -133,10 +133,10 @@
<div id="admin-menu"> <div id="admin-menu">
<div class="menu-title"><?= $lang['adminmenu'] ?></div> <div class="menu-title"><?= $lang['adminmenu'] ?></div>
<?php if ($auth->isAdmin()): ?> <?php if ($auth->isAdmin()): ?>
<a id="adduser" class="menu-link"><?= $lang['adduser'] ?></a> <a id="adduser" class="menu-link" data-bind="onUserAdd"><?= $lang['adduser'] ?></a>
<a id="edituser" class="menu-link"><?= $lang['edituser'] ?></a> <a id="edituser" class="menu-link" data-bind="onUserEdit"><?= $lang['edituser'] ?></a>
<?php endif; ?> <?php endif; ?>
<a id="edittrack" class="menu-link"><?= $lang['edittrack'] ?></a> <a id="edittrack" class="menu-link" data-bind="onTrackEdit"><?= $lang['edittrack'] ?></a>
</div> </div>
<?php endif; ?> <?php endif; ?>

View File

@ -18,6 +18,7 @@
*/ */
import { lang as $, auth } from './initializer.js'; import { lang as $, auth } from './initializer.js';
import UserDialogModel from './userdialogmodel.js';
import ViewModel from './viewmodel.js'; import ViewModel from './viewmodel.js';
import uSelect from './select.js'; import uSelect from './select.js';
import uUser from './user.js'; import uUser from './user.js';
@ -36,14 +37,29 @@ export default class UserViewModel extends ViewModel {
/** @type {uUser[]} */ /** @type {uUser[]} */
userList: [], userList: [],
/** @type {string} */ /** @type {string} */
currentUserId: '0' currentUserId: '0',
// click handlers
/** @type {function} */
onUserEdit: null,
/** @type {function} */
onUserAdd: null,
/** @type {function} */
onPasswordChange: null
}); });
this.setClickHandlers();
/** @type HTMLSelectElement */ /** @type HTMLSelectElement */
const listEl = document.querySelector('#user'); const listEl = document.querySelector('#user');
this.editEl = this.getBoundElement('onUserEdit');
this.select = new uSelect(listEl, $._('suser'), `- ${$._('allusers')} -`); this.select = new uSelect(listEl, $._('suser'), `- ${$._('allusers')} -`);
this.state = state; this.state = state;
} }
setClickHandlers() {
this.model.onUserEdit = () => this.showDialog('edit');
this.model.onUserAdd = () => this.showDialog('add');
this.model.onPasswordChange = () => this.showDialog('pass');
}
/** /**
* @return {UserViewModel} * @return {UserViewModel}
*/ */
@ -78,6 +94,7 @@ export default class UserViewModel extends ViewModel {
this.onChanged('currentUserId', (listValue) => { this.onChanged('currentUserId', (listValue) => {
this.state.showAllUsers = listValue === uSelect.allValue; this.state.showAllUsers = listValue === uSelect.allValue;
this.state.currentUser = this.model.userList.find((_user) => _user.listValue === listValue) || null; this.state.currentUser = this.model.userList.find((_user) => _user.listValue === listValue) || null;
UserViewModel.setMenuVisible(this.editEl, this.state.currentUser !== null && !this.state.currentUser.isEqualTo(auth.user));
}); });
state.onChanged('showLatest', (showLatest) => { state.onChanged('showLatest', (showLatest) => {
if (showLatest) { if (showLatest) {
@ -88,4 +105,38 @@ export default class UserViewModel extends ViewModel {
}); });
} }
showDialog(action) {
const vm = new UserDialogModel(this, action);
vm.init();
}
/**
* @param {uUser} newUser
*/
onUserAdded(newUser) {
this.model.userList.push(newUser);
this.model.userList.sort((a, b) => ((a.login > b.login) ? 1 : -1));
}
onUserDeleted() {
const index = this.model.userList.indexOf(this.state.currentUser);
this.state.currentUser = null;
if (index !== -1) {
this.model.userList.splice(index, 1);
if (this.model.userList.length) {
this.model.currentUserId = this.model.userList[index].listValue;
} else {
this.model.currentUserId = '0';
}
}
}
static setMenuVisible(el, visible) {
if (visible) {
el.classList.remove('menu-hidden');
} else {
el.classList.add('menu-hidden');
}
}
} }

View File

@ -5,7 +5,7 @@
<div> <div>
<a data-bind="onShowUserMenu"><img class="icon" alt="User" src="images/user.svg"> testUser</a> <a data-bind="onShowUserMenu"><img class="icon" alt="User" src="images/user.svg"> testUser</a>
<div id="user-menu" class="menu-hidden"> <div id="user-menu" class="menu-hidden">
<a id="user-pass"><img class="icon" alt="Change password" src="images/lock.svg"> Change password</a> <a id="user-pass" data-bind="onPasswordChange"><img class="icon" alt="Change password" src="images/lock.svg"> Change password</a>
<a href="utils/logout.php"><img class="icon" alt="Log out" src="images/poweroff.svg"> Log out</a> <a href="utils/logout.php"><img class="icon" alt="Log out" src="images/poweroff.svg"> Log out</a>
</div> </div>
</div> </div>
@ -80,9 +80,9 @@
<div id="admin-menu"> <div id="admin-menu">
<div class="menu-title">Administration</div> <div class="menu-title">Administration</div>
<a id="adduser" class="menu-link">Add user</a> <a id="adduser" class="menu-link" data-bind="onUserAdd">Add user</a>
<a id="edituser" class="menu-link">Edit user</a> <a id="edituser" class="menu-link" data-bind="onUserEdit">Edit user</a>
<a id="edittrack" class="menu-link">Edit track</a> <a id="edittrack" class="menu-link" data-bind="onTrackEdit">Edit track</a>
</div> </div>
</div> </div>

View File

@ -33,16 +33,22 @@ describe('UserViewModel tests', () => {
let users; let users;
/** @type {HTMLSelectElement} */ /** @type {HTMLSelectElement} */
let userEl; let userEl;
let userEditEl;
let userAddEl;
let userPassEl;
let vm; let vm;
beforeEach((done) => { beforeEach((done) => {
Fixture.load('main.html') Fixture.load('main-authorized.html')
.then(() => done()) .then(() => done())
.catch((e) => done.fail(e)); .catch((e) => done.fail(e));
}); });
beforeEach(() => { beforeEach(() => {
userEl = document.querySelector('#user'); userEl = document.querySelector('#user');
userEditEl = document.querySelector('#edituser');
userAddEl = document.querySelector('#adduser');
userPassEl = document.querySelector('#user-pass');
config.reinitialize(); config.reinitialize();
lang.init(config); lang.init(config);
lang.strings['suser'] = 'select user'; lang.strings['suser'] = 'select user';
@ -211,4 +217,100 @@ describe('UserViewModel tests', () => {
}, 100); }, 100);
}); });
it('should show user edit dialog on button click', (done) => {
// given
spyOn(vm, 'showDialog');
// when
vm.bindAll();
userEditEl.click();
// then
setTimeout(() => {
expect(vm.showDialog).toHaveBeenCalledWith('edit');
done();
}, 100);
});
it('should show user add dialog on button click', (done) => {
// given
spyOn(vm, 'showDialog');
// when
vm.bindAll();
userAddEl.click();
// then
setTimeout(() => {
expect(vm.showDialog).toHaveBeenCalledWith('add');
done();
}, 100);
});
it('should show password change dialog on button click', (done) => {
// given
spyOn(vm, 'showDialog');
// when
vm.bindAll();
userPassEl.click();
// then
setTimeout(() => {
expect(vm.showDialog).toHaveBeenCalledWith('pass');
done();
}, 100);
});
it('should add new user to user list in alphabetic order', () => {
// given
user1 = new uUser(1, 'b');
user2 = new uUser(2, 'a');
vm.model.userList = [ user1 ];
// when
vm.onUserAdded(user2);
// then
expect(vm.model.userList.length).toBe(2);
expect(vm.model.userList[0]).toBe(user2);
});
it('should remove current user from user list and set new current user id', () => {
// given
vm.model.userList = [ user1, user2 ];
vm.state.currentUser = user1;
vm.model.currentUserId = user1.listValue;
// when
vm.onUserDeleted();
// then
expect(vm.model.userList.length).toBe(1);
expect(vm.model.currentUserId).toBe(user2.listValue);
expect(vm.state.currentUser).toBe(null);
});
it('should remove last remaining element from user list and set empty user id', () => {
// given
vm.model.userList = [ user1 ];
vm.state.currentUser = user1;
vm.model.currentUserId = user1.listValue;
// when
vm.onUserDeleted();
// then
expect(vm.model.userList.length).toBe(0);
expect(vm.model.currentUserId).toBe('0');
expect(vm.state.currentUser).toBe(null);
});
it('show hide element', () => {
// given
const element = document.createElement('div');
// when
UserViewModel.setMenuVisible(element, false);
// then
expect(element.classList.contains('menu-hidden')).toBe(true);
});
it('show shown hidden element', () => {
// given
const element = document.createElement('div');
element.classList.add('menu-hidden');
// when
UserViewModel.setMenuVisible(element, true);
// then
expect(element.classList.contains('menu-hidden')).toBe(false);
});
}); });

View File

@ -30,7 +30,7 @@
$lang = (new uLang(uConfig::$lang))->getStrings(); $lang = (new uLang(uConfig::$lang))->getStrings();
if (!$auth->isAuthenticated() || !$auth->isAdmin() || $auth->user->login == $login || empty($action) || empty($login)) { if (!$auth->isAuthenticated() || !$auth->isAdmin() || $auth->user->login === $login || empty($action) || empty($login)) {
uUtils::exitWithError($lang["servererror"]); uUtils::exitWithError($lang["servererror"]);
} }
@ -45,7 +45,7 @@
if (empty($pass) || ($userId = uUser::add($login, $pass)) === false) { if (empty($pass) || ($userId = uUser::add($login, $pass)) === false) {
uUtils::exitWithError($lang["servererror"]); uUtils::exitWithError($lang["servererror"]);
} else { } else {
$data = [ 'userid' => $userId ]; $data = [ 'id' => $userId ];
} }
break; break;