Overview
Comment:Improve account selector: use keyboard up/down to select an account, only load typed accounts if checkbox is checked
Downloads: Tarball | ZIP archive | SQL archive
Timelines: family | ancestors | descendants | both | dev
Files: files | file ages | folders
SHA1: 83b76b9b29b7623e5eb3982bc1795df2336f0649
User & Date: bohwaz on 2020-12-03 23:28:24
Other Links: branch diff | manifest | tags
Context
2020-12-03
23:29
UX improvements in fees forms check-in: 1c1a6953fd user: bohwaz tags: dev
23:28
Improve account selector: use keyboard up/down to select an account, only load typed accounts if checkbox is checked check-in: 83b76b9b29 user: bohwaz tags: dev
22:39
Order fees by cost, then label check-in: 77c3e9fa9b user: bohwaz tags: dev
Changes

Modified src/templates/acc/charts/accounts/selector.tpl from [d05a782264] to [1e7461b438].

1
2
3
4
5
6
7

8
9
10
11
12
13
14
15
16
17
18
19
20
21

22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
{include file="admin/_head.tpl" title="Sélectionner un compte" body_id="popup" is_popup=true}

{if empty($grouped_accounts) && empty($accounts)}
	<p class="block alert">Le plan comptable ne comporte aucun compte de ce type. Pour afficher des comptes ici, les <a href="{$www_url}admin/acc/charts/accounts/all.php?id={$chart.id}" target="_blank">modifier dans le plan comptable</a> en sélectionnant le type de compte favori voulu.</td>

{elseif isset($grouped_accounts)}


	{foreach from=$grouped_accounts item="group"}
		<h2 class="ruler">{$group.label}</h2>

		<table class="list">
			<tbody>
			{foreach from=$group.accounts item="account"}
				<tr>
					<td>{$account.code}</td>
					<th>{$account.label}</th>
					<td class="desc">{$account.description}</td>
					<td class="actions">
						<button class="icn-btn" value="{$account.id}" data-label="{$account.code} — {$account.label}" data-icon="&rarr;">Sélectionner</button>
					</td>
				</tr>

			{/foreach}
			</tbody>
		</table>
	{/foreach}

{else}

	<h2 class="ruler">
		<input type="text" placeholder="Recherche rapide" id="lookup" />
		<label>{input type="checkbox" name="typed_only" value=1 default=1} N'afficher que les comptes favoris</label>
	</h2>

	<table class="accounts">
		<tbody>
		{foreach from=$accounts item="account"}
			<tr class="account-level-{$account.code|strlen} t{$account.type}">
				<td>{$account.code}</td>
				<th>{$account.label}</th>
				<td>
				{if $account.type}
					{icon shape="star"} <?=Entities\Accounting\Account::TYPES_NAMES[$account->type]?>
				{/if}
				</td>
				<td class="actions">
					<button class="icn-btn" value="{$account.id}" data-label="{$account.code} — {$account.label}" data-icon="&rarr;">Sélectionner</button>
				</td>
			</tr>
		{/foreach}
		</tbody>
	</table>

{/if}

{literal}
<script type="text/javascript">

RegExp.escape = function(string) {
  return string.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&')
};

function normalizeString(str) {
	return str.normalize('NFD').replace(/[\u0300-\u036f]/g, "")
}

var buttons = document.querySelectorAll('button');

buttons.forEach((e) => {
	e.onclick = () => {
		window.parent.g.inputListSelected(e.value, e.getAttribute('data-label'));
	};
});

buttons[0].focus();

var rows = document.querySelectorAll('table tr');

rows.forEach((e) => {
	e.classList.add('clickable');
	var l = e.querySelector('td').innerText + ' ' + e.querySelector('th').innerText;
	e.setAttribute('data-search-label', normalizeString(l));

	e.onclick = (evt) => {
		if (evt.target.tagName && evt.target.tagName == 'BUTTON') {
			return;
		}

		e.querySelector('button').click();
	};
});

var q = document.getElementById('lookup');

if (q) {
	q.onkeyup = (e) => {
		var query = new RegExp(RegExp.escape(normalizeString(q.value)), 'i');

		rows.forEach((elm) => {
			if (elm.getAttribute('data-search-label').match(query)) {
				elm.style.display = null;
			}
			else {
				elm.style.display = 'none';
			}
		});

	};

	q.focus();
}

var o = document.getElementById('f_typed_only_1');

if (o) {
	o.onchange = () => {
		g.toggle('.t0', !o.checked);
	};
	g.toggle('.t0', !o.checked);
}
</script>
{/literal}

{include file="admin/_foot.tpl"}







>






|







>









|





|

















<
|

<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56

57
58

































































59
{include file="admin/_head.tpl" title="Sélectionner un compte" body_id="popup" is_popup=true}

{if empty($grouped_accounts) && empty($accounts)}
	<p class="block alert">Le plan comptable ne comporte aucun compte de ce type. Pour afficher des comptes ici, les <a href="{$www_url}admin/acc/charts/accounts/all.php?id={$chart.id}" target="_blank">modifier dans le plan comptable</a> en sélectionnant le type de compte favori voulu.</td>

{elseif isset($grouped_accounts)}

	<?php $index = 1; ?>
	{foreach from=$grouped_accounts item="group"}
		<h2 class="ruler">{$group.label}</h2>

		<table class="list">
			<tbody>
			{foreach from=$group.accounts item="account"}
				<tr data-idx="{$index}">
					<td>{$account.code}</td>
					<th>{$account.label}</th>
					<td class="desc">{$account.description}</td>
					<td class="actions">
						<button class="icn-btn" value="{$account.id}" data-label="{$account.code} — {$account.label}" data-icon="&rarr;">Sélectionner</button>
					</td>
				</tr>
				<?php $index++; ?>
			{/foreach}
			</tbody>
		</table>
	{/foreach}

{else}

	<h2 class="ruler">
		<input type="text" placeholder="Recherche rapide" id="lookup" />
		<label>{input type="checkbox" name="typed_only" value=0 default=0 default=$all} N'afficher que les comptes favoris</label>
	</h2>

	<table class="accounts">
		<tbody>
		{foreach from=$accounts item="account"}
			<tr data-idx="{$iteration}" class="account-level-{$account.code|strlen}">
				<td>{$account.code}</td>
				<th>{$account.label}</th>
				<td>
				{if $account.type}
					{icon shape="star"} <?=Entities\Accounting\Account::TYPES_NAMES[$account->type]?>
				{/if}
				</td>
				<td class="actions">
					<button class="icn-btn" value="{$account.id}" data-label="{$account.code} — {$account.label}" data-icon="&rarr;">Sélectionner</button>
				</td>
			</tr>
		{/foreach}
		</tbody>
	</table>

{/if}


<script type="text/javascript" src="{$admin_url}static/scripts/selector.js"></script>


































































{include file="admin/_foot.tpl"}

Modified src/www/admin/acc/charts/accounts/selector.php from [df203fcddd] to [17dde2f21a].

37
38
39
40
41
42
43


44
45
46
47
48
49
50

51
52
	throw new UserException('Aucun exercice ouvert disponible');
}

$accounts = $chart->accounts();

$tpl->assign(compact('chart', 'targets'));



if (!$targets) {
	$tpl->assign('accounts', $accounts->listAll());
}
else {
	$tpl->assign('grouped_accounts', $accounts->listCommonGrouped(explode(':', $targets)));
}



$tpl->display('acc/charts/accounts/selector.tpl');







>
>

|





>


37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
	throw new UserException('Aucun exercice ouvert disponible');
}

$accounts = $chart->accounts();

$tpl->assign(compact('chart', 'targets'));

$all = (bool) qg('all');

if (!$targets) {
	$tpl->assign('accounts', !$all ? $accounts->listCommonTypes() : $accounts->listAll());
}
else {
	$tpl->assign('grouped_accounts', $accounts->listCommonGrouped(explode(':', $targets)));
}

$tpl->assign('all', $all);

$tpl->display('acc/charts/accounts/selector.tpl');

Added src/www/admin/static/scripts/selector.js version [049f79c975].







































































































































































































































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
RegExp.escape = function(string) {
  return string.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&')
};

function normalizeString(str) {
	return str.normalize('NFD').replace(/[\u0300-\u036f]/g, "")
}

var buttons = document.querySelectorAll('button');

buttons.forEach((e) => {
	e.onclick = () => {
		window.parent.g.inputListSelected(e.value, e.getAttribute('data-label'));
	};
});

var rows = document.querySelectorAll('table tr');

rows.forEach((e, k) => {
	e.classList.add('clickable');
	var l = e.querySelector('td').innerText + ' ' + e.querySelector('th').innerText;
	e.setAttribute('data-search-label', normalizeString(l));

	e.querySelector('button').onfocus = () => {
		rows.forEach((r) => {
			if (r == e) {
				return;
			}

			r.classList.remove('focused');
		});

		e.classList.add('focused');
	};

	e.onclick = (evt) => {
		if (evt.target.tagName && evt.target.tagName == 'BUTTON') {
			return;
		}

		e.querySelector('button').click();
	};
});

document.onkeydown = (evt) => {
	let focus = document.activeElement;
	let new_focus;

	// Get first element
	if (focus.tagName != 'BUTTON') {
		new_focus = document.querySelector('table tr');
	}

	if (evt.key == 'ArrowUp' && !new_focus) {
		let idx = focus.parentNode.parentNode.dataset.idx - 1;

		if (idx == 0) {
			return true;
		}

		new_focus = rows[idx - 1];
	}
	else if (evt.key == 'ArrowDown' && !new_focus) {
		let idx = focus.parentNode.parentNode.dataset.idx - 1;

		if (idx >= rows.length - 1) {
			return true;
		}

		new_focus = rows[idx + 1];
	}
	else {
		new_focus = null;
	}

	if (!new_focus) {
		return true;
	}

	new_focus.querySelector('button').focus();
	return false;
};

buttons[0].focus();

var q = document.getElementById('lookup');

if (q) {
	q.onkeyup = (e) => {
		var query = new RegExp(RegExp.escape(normalizeString(q.value)), 'i');

		rows.forEach((elm) => {
			if (elm.getAttribute('data-search-label').match(query)) {
				elm.style.display = null;
			}
			else {
				elm.style.display = 'none';
			}
		});

		return false;
	};

	q.focus();
}

var o = document.getElementById('f_typed_only_0');

if (o) {
	o.onchange = () => {
		let s = new URLSearchParams(window.location.search);
		s.set("all", o.checked ? 0 : 1);
		window.location.search = s.toString();
	};
}