Skip to content
Snippets Groups Projects
Commit a6f88903 authored by Jan-Hendrik Willms's avatar Jan-Hendrik Willms
Browse files

prevent tabbing to disabled action menu links and in institute basic data, fixes #3010, fixes #3007

Closes #3010 and #3007

Merge request studip/studip!2039
parent 2e9b92b7
No related branches found
No related tags found
No related merge requests found
...@@ -76,7 +76,7 @@ class Institute_BasicdataController extends AuthenticatedController ...@@ -76,7 +76,7 @@ class Institute_BasicdataController extends AuthenticatedController
$this->question = (string) QuestionBox::create( $this->question = (string) QuestionBox::create(
$message, $message,
$this->url_for('institute/basicdata/delete/' . $i_view, $post), $this->url_for('institute/basicdata/delete/' . $i_view, $post),
$this->url_for('institute/basicdata/delete/' . $i_view, []) $this->url_for('institute/basicdata/index/' . $i_view)
); );
} }
......
...@@ -143,6 +143,7 @@ class ActionMenu ...@@ -143,6 +143,7 @@ class ActionMenu
'attributes' => $attributes, 'attributes' => $attributes,
]; ];
} }
$index = $index ?: md5($action['link'] . json_encode($action['attributes'])); $index = $index ?: md5($action['link'] . json_encode($action['attributes']));
$action['index'] = $index; $action['index'] = $index;
//now insert it possibly at the desired position: //now insert it possibly at the desired position:
...@@ -326,10 +327,10 @@ class ActionMenu ...@@ -326,10 +327,10 @@ class ActionMenu
: self::TEMPLATE_FILE_MULTIPLE; : self::TEMPLATE_FILE_MULTIPLE;
; ;
$template = $GLOBALS['template_factory']->open($template_file); $template = $GLOBALS['template_factory']->open($template_file);
$template->actions = array_map(function ($action) { $template->actions = array_map(function ($action): array {
$disabled = isset($action['attributes']['disabled']) $action['disabled'] = isset($action['attributes']['disabled'])
&& $action['attributes']['disabled'] !== false; && $action['attributes']['disabled'] !== false;
if ($disabled && $action['icon']) { if ($action['disabled'] && $action['icon']) {
$action['icon'] = $action['icon']->copyWithRole(Icon::ROLE_INACTIVE); $action['icon'] = $action['icon']->copyWithRole(Icon::ROLE_INACTIVE);
} }
return $action; return $action;
......
...@@ -35,6 +35,13 @@ class LinkButton extends Interactable ...@@ -35,6 +35,13 @@ class LinkButton extends Interactable
*/ */
public function __toString() public function __toString()
{ {
if (
isset($this->attributes['disabled'])
&& $this->attributes['disabled'] !== false
) {
return (string) Button::create($this->label, 'none', $this->attributes);
}
// add "button" to attribute @class // add "button" to attribute @class
if (empty($this->attributes['class'])) { if (empty($this->attributes['class'])) {
$this->attributes['class'] = ''; $this->attributes['class'] = '';
......
...@@ -102,21 +102,7 @@ $action-menu-shadow: 1px 1px 1px var(--dark-gray-color-60); ...@@ -102,21 +102,7 @@ $action-menu-shadow: 1px 1px 1px var(--dark-gray-color-60);
display: block; display: block;
} }
&.action-menu-item-disabled { .action-menu-item-icon {
> a,
> label {
&,
&:hover {
color: var(--dark-gray-color-80);
cursor: default;
}
}
}
a img,
a svg,
.action-menu-no-icon,
input[type="image"] {
display: inline-block; display: inline-block;
margin: 0 0.25em; margin: 0 0.25em;
vertical-align: middle; vertical-align: middle;
...@@ -147,6 +133,18 @@ $action-menu-shadow: 1px 1px 1px var(--dark-gray-color-60); ...@@ -147,6 +133,18 @@ $action-menu-shadow: 1px 1px 1px var(--dark-gray-color-60);
border-top: thin solid var(--dark-gray-color-45); border-top: thin solid var(--dark-gray-color-45);
margin: 4px 0; margin: 4px 0;
} }
&.action-menu-item-disabled {
> label,
> button {
color: var(--dark-gray-color-80);
cursor: default;
&:hover {
color: var(--dark-gray-color-80);
}
}
}
} }
&.is-open { &.is-open {
......
...@@ -10,28 +10,40 @@ ...@@ -10,28 +10,40 @@
{{ title }} {{ title }}
</div> </div>
<ul class="action-menu-list"> <ul class="action-menu-list">
<li v-for="item in navigationItems" :key="item.id" class="action-menu-item"> <li v-for="item in navigationItems" :key="item.id"
<hr v-if="item.type === 'separator'"> class="action-menu-item"
<a v-else-if="item.type === 'link'" v-bind="linkAttributes(item)" v-on="linkEvents(item)"> :class="{'action-menu-item-disabled': item.disabled}"
<studip-icon v-if="item.icon !== false" :shape="item.icon.shape" :role="item.icon.role"></studip-icon> >
<label v-if="item.disabled" aria-disabled="true" v-bind="item.attributes">
<studip-icon v-if="item.icon"
:shape="item.icon"
role="inactive"
class="action-menu-item-icon"
/>
<span v-else class="action-menu-no-icon"></span> <span v-else class="action-menu-no-icon"></span>
{{ item.label }}
</a>
<label v-else-if="item.type === 'checkbox'" class="undecorated" v-on="linkEvents(item)">
<studip-icon :shape="item.checked ? 'checkbox-checked' : 'checkbox-unchecked'" :role="item.icon.role" :name="item.name" :title="item.label" aria-role="checkbox" :aria-checked="item.checked.toString()" v-bind="linkAttributes(item)"></studip-icon>
{{ item.label }} {{ item.label }}
</label> </label>
<label v-else-if="item.type === 'radio'" class="undecorated" v-on="linkEvents(item)"> <hr v-else-if="item.type === 'separator'">
<studip-icon :shape="item.checked ? 'radiobutton-checked' : 'radiobutton-unchecked'" :role="item.icon.role" :name="item.name" :title="item.label" aria-role="radio" :aria-checked="item.checked.toString()" v-bind="linkAttributes(item)"></studip-icon> <a v-else-if="item.type === 'link'" v-bind="item.attributes" v-on="linkEvents(item)">
<studip-icon v-if="item.icon"
:shape="item.icon"
class="action-menu-item-icon"
/>
<span v-else class="action-menu-no-icon"></span>
{{ item.label }} {{ item.label }}
</label> </a>
<label v-else-if="item.icon" class="undecorated" v-on="linkEvents(item)"> <label v-else-if="item.icon" class="undecorated" v-on="linkEvents(item)">
<studip-icon :shape="item.icon.shape" :role="item.icon.role" :name="item.name" :title="item.label" v-bind="linkAttributes(item)"></studip-icon> <studip-icon :shape="item.icon"
:name="item.name"
class="action-menu-item-icon"
v-bind="item.attributes"
/>
{{ item.label }} {{ item.label }}
</label> </label>
<template v-else> <template v-else>
<span class="action-menu-no-icon"></span> <span class="action-menu-no-icon"></span>
<button :name="item.name" v-bind="linkAttributes(item)" v-on="linkEvents(item)"> <button :name="item.name" v-bind="item.attributes" v-on="linkEvents(item)">
{{ item.label }} {{ item.label }}
</button> </button>
</template> </template>
...@@ -40,10 +52,22 @@ ...@@ -40,10 +52,22 @@
</div> </div>
</div> </div>
<div v-else> <div v-else>
<a v-for="item in navigationItems" :key="item.id" v-bind="linkAttributes(item)" v-on="linkEvents(item)"> <template v-for="item in navigationItems">
<span v-if="item.type === 'separator'" class="quiet">|</span> <label v-if="item.disabled" :key="item.id" aria-disabled="true" v-bind="item.attributes">
<studip-icon v-else :title="item.label" :shape="item.icon.shape" :role="item.icon.role" :size="20"></studip-icon> <studip-icon :shape="item.icon"
:title="item.label"
role="inactive"
class="action-menu-item-icon"
/>
</label>
<span v-else-if="item.type === 'separator'" :key="item.id" class="quiet">|</span>
<a v-else :key="item.id" v-bind="item.attributes" v-on="linkEvents(item)">
<studip-icon :shape="item.icon"
:title="item.label"
class="action-menu-item-icon"
></studip-icon>
</a> </a>
</template>
</div> </div>
</template> </template>
...@@ -72,26 +96,12 @@ export default { ...@@ -72,26 +96,12 @@ export default {
}; };
}, },
methods: { methods: {
linkAttributes (item) {
let attributes = item.attributes;
attributes.class = item.classes;
if (item.disabled) {
attributes.disabled = true;
}
if (item.url) {
attributes.href = item.url;
}
return attributes;
},
linkEvents (item) { linkEvents (item) {
let events = {}; let events = {};
if (item.emit) { if (item.emit) {
events.click = (e) => { events.click = (e) => {
e.preventDefault(); e.preventDefault();
this.$emit.apply(this, [item.emit].concat(item.emitArguments)); this.$emit.apply(this, [item.emit].concat(item.emitArguments ?? []));
this.close(); this.close();
}; };
} }
...@@ -104,26 +114,22 @@ export default { ...@@ -104,26 +114,22 @@ export default {
computed: { computed: {
navigationItems () { navigationItems () {
return this.items.map((item) => { return this.items.map((item) => {
let classes = item.classes ?? ''; item.type = item.type ?? 'link';
if (item.disabled) { item.attributes = item.attributes ?? {};
classes += " action-menu-item-disabled";
if (item.type === 'link') {
item.attributes.href = item.url ?? '#';
} else if (item.type === 'checkbox') {
item.attributes['aria-role'] = item.type;
item.attributes['aria-checked'] = item.checked.toString();
item.icon = item.checked ? 'checkbox-checked' : 'checkbox-unchecked';
} else if (item.type === 'radio') {
item.attributes['aria-role'] = item.type;
item.attributes['aria-checked'] = item.checked.toString();
item.icon = item.checked ? 'radiobutton-checked' : 'radiobutton-unchecked';
} }
return {
label: item.label, return item;
url: item.url || '#',
emit: item.emit || false,
emitArguments: item.emitArguments || [],
icon: item.icon ? {
shape: item.icon,
role: item.disabled ? 'inactive' : 'clickable'
} : false,
type: item.type || 'link',
name: item.name ?? null,
classes: classes.trim(),
attributes: item.attributes || {},
disabled: item.disabled,
checked: item.checked,
};
}); });
}, },
shouldCollapse () { shouldCollapse () {
......
...@@ -77,7 +77,7 @@ export default { ...@@ -77,7 +77,7 @@ export default {
label: this.$gettext('Veranstaltungsdetails'), label: this.$gettext('Veranstaltungsdetails'),
icon: 'info-circle', icon: 'info-circle',
attributes: { attributes: {
'data-dialog': '' 'data-dialog': '',
}, },
}); });
} }
......
...@@ -12,17 +12,29 @@ ...@@ -12,17 +12,29 @@
*/ */
?> ?>
<? foreach ($actions as $action): ?> <? foreach ($actions as $action): ?>
<? if ($action['type'] === 'link'): ?> <? if ($action['disabled']): ?>
<label class="undecorated action-menu-item-disabled" aria-disabled="true" <?= arrayToHtmlAttributes($action['attributes'] + ['title' => $action['label']]) ?>>
<? if ($action['icon']): ?>
<?= $action['icon']->asImg(['class' => 'action-menu-item-icon']) ?>
<? else: ?>
<?= htmlReady($action['label']) ?>
<? endif ?>
</label>
<? elseif ($action['type'] === 'link'): ?>
<a href="<?= htmlReady($action['link']) ?>" <?= arrayToHtmlAttributes($action['attributes'] + ['title' => $action['label']]) ?>> <a href="<?= htmlReady($action['link']) ?>" <?= arrayToHtmlAttributes($action['attributes'] + ['title' => $action['label']]) ?>>
<? if ($action['icon']): ?> <? if ($action['icon']): ?>
<?= $action['icon']->asImg() ?> <?= $action['icon']->asImg(['class' => 'action-menu-item-icon']) ?>
<? else: ?> <? else: ?>
<?= htmlReady($action['label']) ?> <?= htmlReady($action['label']) ?>
<? endif ?> <? endif ?>
</a> </a>
<? elseif ($action['type'] === 'button'): ?> <? elseif ($action['type'] === 'button'): ?>
<? if ($action['icon']): ?> <? if ($action['icon']): ?>
<?= $action['icon']->asInput($action['attributes'] + ['name' => $action['name'], 'title' => $action['label']]) ?> <?= $action['icon']->asInput($action['attributes'] + [
'class' => 'action-menu-item-icon',
'name' => $action['name'],
'title' => $action['label'],
]) ?>
<? else: ?> <? else: ?>
<button name="<?= htmlReady($action['name']) ?>" <?= arrayToHtmlAttributes($action['attributes']) ?>> <button name="<?= htmlReady($action['name']) ?>" <?= arrayToHtmlAttributes($action['attributes']) ?>>
<?= htmlReady($action['label']) ?> <?= htmlReady($action['label']) ?>
......
...@@ -9,6 +9,9 @@ ...@@ -9,6 +9,9 @@
* icon: Icon, * icon: Icon,
* attributes: array * attributes: array
* }> $actions * }> $actions
* @var string $title
* @var string $action_menu_title
* @var array $attributes
*/ */
?> ?>
<? // class "action-menu" will be set from API ?> <? // class "action-menu" will be set from API ?>
...@@ -24,11 +27,21 @@ ...@@ -24,11 +27,21 @@
</div> </div>
<ul class="action-menu-list" aria-label="<?= htmlReady($title) ?>"> <ul class="action-menu-list" aria-label="<?= htmlReady($title) ?>">
<? foreach ($actions as $action): ?> <? foreach ($actions as $action): ?>
<li class="action-menu-item <? if (isset($action['attributes']['disabled'])) echo 'action-menu-item-disabled'; ?>"> <li class="action-menu-item <? if ($action['disabled']) echo 'action-menu-item-disabled'; ?>">
<? if ($action['type'] === 'link'): ?> <? if ($action['disabled']): ?>
<label class="undecorated" aria-disabled="true" <?= arrayToHtmlAttributes($action['attributes']) ?>>
<? if ($action['icon']): ?>
<?= $action['icon']->asImg(false, ['class' => 'action-menu-item-icon']) ?>
<? else: ?>
<span class="action-menu-no-icon"></span>
<? endif ?>
<?= htmlReady($action['label']) ?>
</label>
<? elseif ($action['type'] === 'link'): ?>
<a href="<?= htmlReady($action['link']) ?>" <?= arrayToHtmlAttributes($action['attributes']) ?>> <a href="<?= htmlReady($action['link']) ?>" <?= arrayToHtmlAttributes($action['attributes']) ?>>
<? if ($action['icon']): ?> <? if ($action['icon']): ?>
<?= $action['icon']->asImg(false) ?> <?= $action['icon']->asImg(false, ['class' => 'action-menu-item-icon']) ?>
<? else: ?> <? else: ?>
<span class="action-menu-no-icon"></span> <span class="action-menu-no-icon"></span>
<? endif ?> <? endif ?>
...@@ -37,7 +50,11 @@ ...@@ -37,7 +50,11 @@
<? elseif ($action['type'] === 'button'): ?> <? elseif ($action['type'] === 'button'): ?>
<? if ($action['icon']): ?> <? if ($action['icon']): ?>
<label class="undecorated"> <label class="undecorated">
<?= $action['icon']->asInput(false, $action['attributes'] + ['name' => $action['name'], 'title' => $action['label']]) ?> <?= $action['icon']->asInput(false, $action['attributes'] + [
'class' => 'action-menu-item-icon',
'name' => $action['name'],
'title' => $action['label'],
]) ?>
<?= htmlReady($action['label']) ?> <?= htmlReady($action['label']) ?>
</label> </label>
<? else: ?> <? else: ?>
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment