5 commits - plugins/calendar plugins/libcalendaring plugins/tasklist
Thomas Brüderli
bruederli at kolabsys.com
Fri Jun 20 15:12:47 CEST 2014
plugins/calendar/calendar_ui.js | 7
plugins/calendar/drivers/kolab/kolab_driver.php | 6
plugins/calendar/skins/larry/calendar.css | 8
plugins/calendar/skins/larry/templates/calendar.html | 2
plugins/libcalendaring/libcalendaring.php | 25 +-
plugins/libcalendaring/localization/en_US.inc | 1
plugins/tasklist/drivers/kolab/tasklist_kolab_driver.php | 1
plugins/tasklist/jquery.tagedit.js | 100 +++++++--
plugins/tasklist/localization/en_US.inc | 17 +
plugins/tasklist/skins/larry/tagedit.css | 8
plugins/tasklist/skins/larry/tasklist.css | 50 ++--
plugins/tasklist/skins/larry/templates/mainview.html | 114 +++++-----
plugins/tasklist/skins/larry/templates/taskedit.html | 33 +-
plugins/tasklist/tasklist.js | 166 ++++++++++++---
plugins/tasklist/tasklist_ui.php | 15 -
15 files changed, 392 insertions(+), 161 deletions(-)
New commits:
commit 5c490914fecbc257e3b3ba04ddfcdfb2656937f9
Author: Thomas Bruederli <bruederli at kolabsys.com>
Date: Fri Jun 20 15:12:11 2014 +0200
Accessibilty improvements and keyboard navigation for tasks module (#3085)
diff --git a/plugins/tasklist/drivers/kolab/tasklist_kolab_driver.php b/plugins/tasklist/drivers/kolab/tasklist_kolab_driver.php
index 715fc74..1999a8e 100644
--- a/plugins/tasklist/drivers/kolab/tasklist_kolab_driver.php
+++ b/plugins/tasklist/drivers/kolab/tasklist_kolab_driver.php
@@ -1148,7 +1148,6 @@ class tasklist_kolab_driver extends tasklist_driver
if (is_array($tab['fields']) && empty($tab['content'])) {
$table = new html_table(array('cols' => 2));
foreach ($tab['fields'] as $col => $colprop) {
- $colprop['id'] = '_'.$col;
$label = !empty($colprop['label']) ? $colprop['label'] : $this->plugin->gettext($col);
$table->add('title', html::label($colprop['id'], Q($label)));
diff --git a/plugins/tasklist/localization/en_US.inc b/plugins/tasklist/localization/en_US.inc
index 6193105..9fd0e3e 100644
--- a/plugins/tasklist/localization/en_US.inc
+++ b/plugins/tasklist/localization/en_US.inc
@@ -8,8 +8,12 @@ $labels['tags'] = 'Tags';
$labels['tasklistsubscribe'] = 'List permanently';
$labels['listsearchresults'] = 'Available Tasklists';
$labels['findlists'] = 'Find tasklists...';
+$labels['searchterms'] = 'Search terms';
+$labels['notasklistsfound'] = 'No tasklists found';
+$labels['nrtasklistsfound'] = '$nr tasklists found';
$labels['newtask'] = 'New Task';
+$labels['createtask'] = 'Create Task <Enter>';
$labels['createnewtask'] = 'Create new Task (e.g. Saturday, Mow the lawn)';
$labels['createfrommail'] = 'Save as task';
$labels['mark'] = 'Mark';
@@ -19,7 +23,9 @@ $labels['delete'] = 'Delete';
$labels['title'] = 'Title';
$labels['description'] = 'Description';
$labels['datetime'] = 'Due';
+$labels['duetime'] = 'Due time';
$labels['start'] = 'Start';
+$labels['starttime'] = 'Start time';
$labels['alarms'] = 'Reminder';
$labels['repeat'] = 'Repeat';
$labels['status'] = 'Status';
@@ -48,6 +54,7 @@ $labels['addsubtask'] = 'Add subtask';
$labels['deletetask'] = 'Delete task';
$labels['deletethisonly'] = 'Delete this task only';
$labels['deletewithchilds'] = 'Delete with all subtasks';
+$labels['taskactions'] = 'Task options...';
$labels['tabsummary'] = 'Summary';
$labels['tabrecurrence'] = 'Recurrence';
@@ -79,3 +86,13 @@ $labels['deleteparenttasktconfirm'] = 'Do you really want to delete this task an
$labels['deletelistconfirm'] = 'Do you really want to delete this list with all its tasks?';
$labels['deletelistconfirmrecursive'] = 'Do you really want to delete this list with all its sub-lists and tasks?';
$labels['aclnorights'] = 'You do not have administrator rights on this task list.';
+
+// (hidden) titles and labels for accessibility annotations
+$labels['quickaddinput'] = 'New task date and title';
+$labels['arialabelquickaddbox'] = 'Quick add new task';
+$labels['arialabelsearchform'] = 'Task search form';
+$labels['arialabelquicksearchbox'] = 'Task search input';
+$labels['arialabellistsearchform'] = 'Tasklists search form';
+$labels['arialabeltaskselector'] = 'List mode';
+$labels['arialabeltasklisting'] = 'Tasks listing';
+
diff --git a/plugins/tasklist/skins/larry/tagedit.css b/plugins/tasklist/skins/larry/tagedit.css
index a26b5ab..704427e 100644
--- a/plugins/tasklist/skins/larry/tagedit.css
+++ b/plugins/tasklist/skins/larry/tagedit.css
@@ -63,6 +63,14 @@
color: #0d5165;
}
+.tagedit-list li.tagedit-listelement-focus {
+ border-color: #4787b1;
+ -moz-box-shadow: 0 0 3px 1px rgba(71,135,177, 0.8);
+ -webkit-box-shadow: 0 0 3px 1px rgba(71,135,177, 0.8);
+ -o-box-shadow: 0 0 3px 1px rgba(71,135,177, 0.8);
+ box-shadow: 0 0 3px 1px rgba(71,135,177, 0.8);
+}
+
.tagedit span.tag-element {
margin-right: 0.6em;
padding: 2px 6px;
diff --git a/plugins/tasklist/skins/larry/tasklist.css b/plugins/tasklist/skins/larry/tasklist.css
index 54372fa..0bcf4fe 100644
--- a/plugins/tasklist/skins/larry/tasklist.css
+++ b/plugins/tasklist/skins/larry/tasklist.css
@@ -165,7 +165,6 @@ body.attachmentwin #topnav .topright {
border-color: #003645;
border-radius: 10px;
text-shadow: none;
- outline: none;
}
#taskselector li .count {
@@ -306,15 +305,17 @@ body.attachmentwin #topnav .topright {
background: url(sprites.png) right 20px no-repeat;
}
-#tasklistsbox .treelist li span.quickview {
+#tasklistsbox .treelist li a.quickview {
display: inline-block;
position: absolute;
top: 6px;
- right: 20px;
+ right: 24px;
width: 16px;
height: 16px;
- margin-right: 4px;
+ padding: 0;
background: url(sprites.png) -200px 0 no-repeat;
+ overflow: hidden;
+ text-indent: -5000px;
cursor: pointer;
}
@@ -332,6 +333,7 @@ body.attachmentwin #topnav .topright {
cursor: pointer;
}
+#tasklistsbox .treelist div > a.subscribed:focus,
#tasklistsbox .treelist div:hover > a.subscribed {
background-position: -2px -215px;
}
@@ -340,18 +342,25 @@ body.attachmentwin #topnav .topright {
background-position: -20px -215px;
}
-#tasklistsbox .treelist li div:hover > span.quickview {
+#tasklistsbox .treelist div > a.quickview:focus,
+#tasklistsbox .treelist li div:hover > a.quickview {
background-position: -20px -101px;
}
-#tasklistsbox .treelist li div.focusview > span.quickview {
+#tasklistsbox .treelist li div.focusview > a.quickview {
background-position: -2px -101px;
}
-#tasklistsbox .searchresults .treelist li span.quickview {
+#tasklistsbox .searchresults .treelist li a.quickview {
display: none;
}
+#tasklistsbox .treelist div a.quickview:focus,
+#tasklistsbox .treelist div a.subscribed:focus {
+ border-radius: 3px;
+ outline: 2px solid rgba(30,150,192, 0.5);
+}
+
#tasklistsbox .treelist li.selected > div > span.listname {
font-weight: bold;
}
@@ -578,8 +587,10 @@ body.attachmentwin #topnav .topright {
overflow: hidden;
text-overflow: ellipsis;
cursor: default;
+ outline: none;
}
+.taskhead:focus,
.taskhead.droptarget {
border-color: #4787b1;
box-shadow: 0 0 5px 2px rgba(71,135,177, 0.9);
@@ -598,21 +609,20 @@ body.attachmentwin #topnav .topright {
.taskhead .flagged {
display: inline-block;
- visibility: hidden;
width: 16px;
height: 16px;
- background: url(sprites.png) -2px -3px no-repeat;
+ background: url(sprites.png) 1000px -3px no-repeat;
margin: -3px 1em 0 0;
vertical-align: middle;
cursor: pointer;
}
+.taskhead .flagged:focus,
.taskhead:hover .flagged {
- visibility: visible;
+ background-position: -2px -3px;
}
.taskhead.flagged .flagged {
- visibility: visible;
background-position: -2px -23px;
}
@@ -675,28 +685,22 @@ body.attachmentwin #topnav .topright {
font-size: 11px;
}
-.taskhead .actions,
-.taskhead .delete {
+.taskhead .actions {
display: block;
- visibility: hidden;
position: absolute;
top: 3px;
right: 6px;
width: 18px;
height: 18px;
- background: url(sprites.png) 0 -80px no-repeat;
- text-indent: -1000px;
+ background: url(sprites.png) 1000px -80px no-repeat;
+ text-indent: -5000px;
overflow: hidden;
cursor: pointer;
}
-.taskhead .delete {
- background-position: 0 -40px;
-}
-
-.taskhead:hover .actions,
-.taskhead:hover .delete {
- visibility: visible;
+.taskhead .actions:focus,
+.taskhead:hover .actions {
+ background-position: 0 -80px;
}
.taskhead.complete {
diff --git a/plugins/tasklist/skins/larry/templates/mainview.html b/plugins/tasklist/skins/larry/templates/mainview.html
index 632abcb..5a2baf9 100644
--- a/plugins/tasklist/skins/larry/templates/mainview.html
+++ b/plugins/tasklist/skins/larry/templates/mainview.html
@@ -9,109 +9,123 @@
<roundcube:include file="/includes/header.html" />
+<h1 class="voice"><roundcube:label name="tasklist.navtitle" /></h1>
+
<div id="mainscreen">
<div id="sidebar">
- <div id="taskstoolbar" class="toolbar">
+ <h2 id="aria-label-toolbar" class="voice"><roundcube:label name="arialabeltoolbar" /></h2>
+ <div id="taskstoolbar" class="toolbar" role="toolbar" aria-labelledby="aria-label-toolbar">
<roundcube:button command="newtask" type="link" class="button newtask disabled" classAct="button newtask" classSel="button newtask pressed" label="tasklist.newtask" title="tasklist.newtask" />
<roundcube:container name="toolbar" id="taskstoolbar" />
</div>
<div id="tagsbox" class="uibox listbox">
- <h2 class="boxtitle"><roundcube:label name="tasklist.tags" id="taglist" /></h2>
+ <h2 class="boxtitle" id="aria-label-tagsbox"><roundcube:label name="tasklist.tags" id="taglist" /></h2>
<div class="scroller">
- <roundcube:object name="plugin.tagslist" id="tagslist" class="tagcloud" />
+ <roundcube:object name="plugin.tagslist" id="tagslist" class="tagcloud" role="region" aria-labelledby="aria-label-tagsbox" aria-controls="thelist" />
</div>
</div>
- <div id="tasklistsbox" class="uibox listbox">
- <h2 class="boxtitle"><roundcube:label name="tasklist.lists" />
- <a class="iconbutton search" title="<roundcube:label name='tasklist.findlists' />"></a>
+ <div id="tasklistsbox" class="uibox listbox" role="navigation" aria-labelledby="aria-label-tasklists">
+ <h2 class="boxtitle" id="aria-label-tasklists"><roundcube:label name="tasklist.lists" />
+ <a href="#tasklists" class="iconbutton search" title="<roundcube:label name='tasklist.findlists' />" tabindex="0"><roundcube:label name="tasklist.findlists" /></a>
</h2>
<div class="listsearchbox">
- <div class="searchbox">
+ <div class="searchbox" role="search" aria-labelledby="aria-label-listsearchform" aria-controls="tasklists">
+ <h3 id="aria-label-listsearchform" class="voice"><roundcube:label name="tasklist.arialabellistsearchform" /></h3>
+ <label for="tasklistsearch" class="voice"><roundcube:label name="tasklist.searchterms" /></label>
<input type="text" name="q" id="tasklistsearch" placeholder="<roundcube:label name='tasklist.findlists' />" />
<a class="iconbutton searchicon"></a>
- <roundcube:button command="reset-listsearch" id="tasklistsearch-reset" class="iconbutton reset" title="resetsearch" content="x" />
+ <roundcube:button command="reset-listsearch" id="tasklistsearch-reset" class="iconbutton reset" title="resetsearch" label="resetsearch" />
</div>
</div>
<div class="scroller withfooter">
<roundcube:object name="plugin.tasklists" id="tasklists" class="treelist listing" />
</div>
<div class="boxfooter">
- <roundcube:button command="list-create" type="link" title="tasklist.createlist" class="listbutton add disabled" classAct="listbutton add" innerClass="inner" content="+" /><roundcube:button name="tasklistoptionslink" id="tasklistoptionsmenulink" type="link" title="tasklist.listactions" class="listbutton groupactions" onclick="UI.show_popup('tasklistoptionsmenu', undefined, { above:true });return false" innerClass="inner" content="⚙" />
+ <roundcube:button command="list-create" type="link" title="tasklist.createlist" class="listbutton add disabled" classAct="listbutton add" innerClass="inner" label="tasklist.createlist" /><roundcube:button name="tasklistoptionslink" id="tasklistoptionsmenulink" type="link" title="tasklist.listactions" class="listbutton groupactions" onclick="return UI.toggle_popup('tasklistoptionsmenu', event, { above:true })" innerClass="inner" label="tasklist.listactions" aria-haspopup="true" aria-expanded="false" aria-owns="tasklistoptionsmenu-menu" />
</div>
</div>
+
+ <div id="tasklistoptionsmenu" class="popupmenu" aria-hidden="true">
+ <h3 id="aria-label-tasklistoptions" class="voice"><roundcube:label name="tasklist.listactions" /></h3>
+ <ul class="toolbarmenu" id="tasklistoptionsmenu-menu" role="menu" aria-labelledby="aria-label-tasklistoptions">
+ <li role="menuitem"><roundcube:button command="list-edit" label="edit" classAct="active" /></li>
+ <li role="menuitem"><roundcube:button command="list-remove" label="delete" classAct="active" /></li>
+ <!--<li role="menuitem"><roundcube:button command="list-import" label="tasklist.import" classAct="active" /></li>-->
+ <roundcube:if condition="env:tasklist_driver == 'kolab'" />
+ <li role="menuitem"><roundcube:button command="folders" task="settings" type="link" label="managefolders" classAct="active" /></li>
+ <roundcube:endif />
+ </ul>
+ </div>
</div>
<div id="mainview-right">
- <div id="quickaddbox">
+ <div id="quickaddbox" role="region" aria-labelledby="aria-label-quickaddbox">
+ <h2 id="aria-label-quickaddbox" class="voice"><roundcube:label name="tasklist.arialabelquickaddbox" /></h2>
<roundcube:object name="plugin.quickaddform" />
</div>
- <div id="quicksearchbar">
+ <div id="quicksearchbar" role="search" aria-labelledby="aria-label-searchform">
+ <h2 id="aria-label-searchform" class="voice"><roundcube:label name="tasklist.arialabelsearchform" /></h2>
+ <label for="quicksearchbox" class="voice"><roundcube:label name="tasklist.arialabelquicksearchbox" /></label>
<roundcube:object name="plugin.searchform" id="quicksearchbox" />
<a id="searchmenulink" class="iconbutton searchoptions" > </a>
- <roundcube:button command="reset-search" id="searchreset" class="iconbutton reset" title="resetsearch" content=" " />
+ <roundcube:button command="reset-search" id="searchreset" class="iconbutton reset" title="resetsearch" label="resetsearch" />
</div>
<div id="tasksview" class="uibox">
<div class="boxtitle buttonbar">
- <ul id="taskselector">
- <li class="all selected"><a href="#all"><roundcube:label name="tasklist.all" /><span class="count"></span></a></li>
- <li class="overdue inactive"><a href="#overdue"><roundcube:label name="tasklist.overdue" /><span class="count"></span></a></li>
- <li class="flagged"><a href="#flagged"><roundcube:label name="tasklist.flagged" /><span class="count"></span></a></li>
- <li class="today"><a href="#today"><roundcube:label name="tasklist.today" /><span class="count"></span></a></li>
- <li class="tomorrow"><a href="#tomorrow"><roundcube:label name="tasklist.tomorrow" /><span class="count"></span></a></li>
- <li class="week"><a href="#week"><roundcube:label name="tasklist.next7days" /></a></li>
- <li class="later"><a href="#later"><roundcube:label name="tasklist.later" /></a></li>
- <li class="nodate"><a href="#nodate"><roundcube:label name="tasklist.nodate" ucfirst="true" /></a></li>
- <li class="complete"><a href="#complete"><roundcube:label name="tasklist.complete" /><span class="count"></span></a></li>
+ <h2 id="aria-label-taskselector" class="voice"><roundcube:label name="tasklist.arialabeltaskselector" /></h2>
+ <ul id="taskselector" role="radiogroup" aria-labelledby="aria-label-taskselector" aria-controls="thelist">
+ <li class="all selected" role="radio" aria-checked="true" aria-labelledby="aria-radio-all"><a href="#all" id="aria-radio-all"><roundcube:label name="tasklist.all" /><span class="count"></span></a></li>
+ <li class="overdue inactive" role="radio" aria-checked="false" aria-labelledby="aria-radio-overdue"><a href="#overdue" id="aria-radio-overdue"><roundcube:label name="tasklist.overdue" /><span class="count"></span></a></li>
+ <li class="flagged" role="radio" aria-checked="false" aria-labelledby="aria-radio-flagged"><a href="#flagged" id="aria-radio-flagged"><roundcube:label name="tasklist.flagged" /><span class="count"></span></a></li>
+ <li class="today" role="radio" aria-checked="false" aria-labelledby="aria-radio-today"><a href="#today" id="aria-radio-today"><roundcube:label name="tasklist.today" /><span class="count"></span></a></li>
+ <li class="tomorrow" role="radio" aria-checked="false" aria-labelledby="aria-radio-tomorrow"><a href="#tomorrow" id="aria-radio-tomorrow"><roundcube:label name="tasklist.tomorrow" /><span class="count"></span></a></li>
+ <li class="week" role="radio" aria-checked="false" aria-labelledby="aria-radio-week"><a href="#week" id="aria-radio-week"><roundcube:label name="tasklist.next7days" /></a></li>
+ <li class="later" role="radio" aria-checked="false" aria-labelledby="aria-radio-later"><a href="#later" id="aria-radio-later"><roundcube:label name="tasklist.later" /></a></li>
+ <li class="nodate" role="radio" aria-checked="false" aria-labelledby="aria-radio-nodate"><a href="#nodate" id="aria-radio-nodate"><roundcube:label name="tasklist.nodate" ucfirst="true" /></a></li>
+ <li class="complete" role="radio" aria-checked="false" aria-labelledby="aria-radio-complete"><a href="#complete" id="aria-radio-complete"><roundcube:label name="tasklist.complete" /><span class="count"></span></a></li>
</ul>
<div class="buttonbar-right">
- <roundcube:button name="taskviewmenulink" id="taskviewmenulink" type="link" title="tasklist.viewoptions" class="listmenu viewoptions" onclick="UI.show_popup('taskviewmenu');return false" innerClass="inner" content="⚙" />
+ <roundcube:button name="taskviewmenulink" id="taskviewmenulink" type="link" title="tasklist.viewoptions" class="listmenu viewoptions" onclick="return UI.toggle_popup('taskviewmenu',event)" innerClass="inner" label="tasklist.viewoptions" aria-haspopup="true" aria-expanded="false" aria-owns="taskviewmenu-menu" />
+ </div>
+
+ <div id="taskviewmenu" class="popupmenu" aria-hidden="true">
+ <h3 id="aria-label-taskviewmenu" class="voice"><roundcube:label name="tasklist.viewoptions" /></h3>
+ <ul class="toolbarmenu" id="taskviewmenu-menu" role="menu" aria-labelledby="aria-label-taskviewmenu">
+ <li role="menuitem"><roundcube:button command="expand-all" label="expand-all" class="icon" classAct="icon active" innerclass="icon expand" /></li>
+ <li role="menuitem"><roundcube:button command="collapse-all" label="collapse-all" class="icon" classAct="icon active" innerclass="icon collapse" /></li>
+ </ul>
</div>
</div>
- <div class="scroller">
- <roundcube:object name="plugin.tasks" id="thelist" />
+ <h2 class="voice" id="aria-label-tasklisting"><roundcube:label name="tasklist.arialabeltasklisting" /></h2>
+ <div class="scroller" role="main" aria-labelledby="aria-label-tasklisting">
+ <roundcube:object name="plugin.tasks" id="thelist" role="tree" />
<div id="listmessagebox"></div>
<div id="rootdroppable"></div>
</div>
</div>
+ <div id="taskitemmenu" class="popupmenu" aria-hidden="true" data-align="right">
+ <h3 id="aria-label-taskactions" class="voice"><roundcube:label name="tasklist.taskactions" /></h3>
+ <ul class="toolbarmenu iconized" id="taskitemmenu-menu" role="menu" aria-labelledby="aria-label-taskactions">
+ <li role="menuitem"><roundcube:button name="edit" type="link" onclick="rctasks.edit_task(rctasks.selected_task.id, 'edit'); return false" label="edit" class="icon active" innerclass="icon edit" /></li>
+ <li role="menuitem"><roundcube:button name="delete" type="link" onclick="rctasks.delete_task(rctasks.selected_task.id); return false" label="delete" class="icon active" innerclass="icon delete" /></li>
+ <li role="menuitem"><roundcube:button name="addchild" type="link" onclick="rctasks.add_childtask(rctasks.selected_task.id); return false" label="tasklist.addsubtask" class="icon active" innerclass="icon add" /></li>
+ </ul>
</div>
-</div>
-
-<roundcube:object name="message" id="messagestack" />
+ </div>
-<div id="taskitemmenu" class="popupmenu">
- <ul class="toolbarmenu iconized">
- <li><roundcube:button name="edit" type="link" onclick="rctasks.edit_task(rctasks.selected_task.id, 'edit'); return false" label="edit" class="icon active" innerclass="icon edit" /></li>
- <li><roundcube:button name="delete" type="link" onclick="rctasks.delete_task(rctasks.selected_task.id); return false" label="delete" class="icon active" innerclass="icon delete" /></li>
- <li><roundcube:button name="addchild" type="link" onclick="rctasks.add_childtask(rctasks.selected_task.id); return false" label="tasklist.addsubtask" class="icon active" innerclass="icon add" /></li>
- </ul>
</div>
-<div id="tasklistoptionsmenu" class="popupmenu">
- <ul class="toolbarmenu">
- <li><roundcube:button command="list-edit" label="edit" classAct="active" /></li>
- <li><roundcube:button command="list-remove" label="delete" classAct="active" /></li>
- <!--<li><roundcube:button command="list-import" label="tasklist.import" classAct="active" /></li>-->
- <roundcube:if condition="env:tasklist_driver == 'kolab'" />
- <li><roundcube:button command="folders" task="settings" type="link" label="managefolders" classAct="active" /></li>
- <roundcube:endif />
- </ul>
-</div>
+<roundcube:object name="message" id="messagestack" />
-<div id="taskviewmenu" class="popupmenu">
- <ul class="toolbarmenu">
- <li><roundcube:button command="expand-all" label="expand-all" class="icon" classAct="icon active" innerclass="icon expand" /></li>
- <li><roundcube:button command="collapse-all" label="collapse-all" class="icon" classAct="icon active" innerclass="icon collapse" /></li>
- </ul>
-</div>
<div id="taskshow">
<div class="form-section" id="task-parent-title"></div>
diff --git a/plugins/tasklist/skins/larry/templates/taskedit.html b/plugins/tasklist/skins/larry/templates/taskedit.html
index 97f604d..988fd5b 100644
--- a/plugins/tasklist/skins/larry/templates/taskedit.html
+++ b/plugins/tasklist/skins/larry/templates/taskedit.html
@@ -8,51 +8,51 @@
<div class="form-section">
<label for="taskedit-title"><roundcube:label name="tasklist.title" /></label>
<br />
- <input type="text" class="text" name="title" id="taskedit-title" size="60" tabindex="1" />
+ <input type="text" class="text" name="title" id="taskedit-title" size="60" />
</div>
<div class="form-section">
<label for="taskedit-description"><roundcube:label name="tasklist.description" /></label>
<br />
- <textarea name="description" id="taskedit-description" class="text" rows="5" cols="60" tabindex="2"></textarea>
+ <textarea name="description" id="taskedit-description" class="text" rows="5" cols="60"></textarea>
</div>
<div class="form-section">
- <label for="taskedit-tags"><roundcube:label name="tasklist.tags" /></label>
- <roundcube:object name="plugin.tags_editline" id="taskedit-tagline" class="tagedit" tabindex="3" />
+ <label for="tagedit-input"><roundcube:label name="tasklist.tags" /></label>
+ <roundcube:object name="plugin.tags_editline" id="taskedit-tagline" class="tagedit" tabindex="0" />
</div>
<div class="form-section">
<label for="taskedit-startdate"><roundcube:label name="tasklist.start" /></label>
- <input type="text" name="startdate" size="10" id="taskedit-startdate" tabindex="23" />
- <input type="text" name="starttime" size="6" id="taskedit-starttime" tabindex="24" />
+ <input type="text" name="startdate" size="10" id="taskedit-startdate" />
+ <input type="text" name="starttime" size="6" id="taskedit-starttime" aria-label="<roundcube:label name='tasklist.starttime' />" />
<a href="#nodate" style="margin-left:1em" class="edit-nodate" rel="#taskedit-startdate,#taskedit-starttime"><roundcube:label name="tasklist.nodate" /></a>
</div>
<div class="form-section">
<label for="taskedit-date"><roundcube:label name="tasklist.datetime" /></label>
- <input type="text" name="date" size="10" id="taskedit-date" tabindex="20" />
- <input type="text" name="time" size="6" id="taskedit-time" tabindex="21" />
+ <input type="text" name="date" size="10" id="taskedit-date" />
+ <input type="text" name="time" size="6" id="taskedit-time" aria-label="<roundcube:label name='tasklist.duetime' />" />
<a href="#nodate" style="margin-left:1em" class="edit-nodate" rel="#taskedit-date,#taskedit-time"><roundcube:label name="tasklist.nodate" /></a>
</div>
<div class="form-section" id="taskedit-alarms">
<div class="edit-alarm-item first">
- <label><roundcube:label name="tasklist.alarms" /></label>
- <roundcube:object name="plugin.alarm_select" />
+ <label for="edit-alarm-item"><roundcube:label name="tasklist.alarms" /></label>
+ <roundcube:object name="plugin.alarm_select" id="edit-alarm-item" />
<span class="edit-alarm-buttons">
- <a href="#add" class="iconbutton add add-alarm">+</a>
- <a href="#delete" class="iconbutton remove delete-alarm">-</a>
+ <a href="#add" class="iconbutton add add-alarm"><roundcube:label name="libcalendaring.addalarm" /></a>
+ <a href="#delete" class="iconbutton remove delete-alarm"><roundcube:label name="libcalendaring.removealarm" /></a>
</span>
</div>
</div>
<div class="form-section">
<label for="taskedit-completeness"><roundcube:label name="tasklist.complete" /></label>
- <input type="text" name="title" id="taskedit-completeness" size="3" tabindex="25" /> %
+ <input type="text" name="title" id="taskedit-completeness" size="3" /> %
<div id="taskedit-completeness-slider"></div>
</div>
<div class="form-section">
<label for="taskedit-status"><roundcube:label name="tasklist.status" /></label>
- <roundcube:object name="plugin.status_select" id="taskedit-status" tabindex="26" />
+ <roundcube:object name="plugin.status_select" id="taskedit-status" />
</div>
<div class="form-section" id="tasklist-select">
<label for="taskedit-tasklist"><roundcube:label name="tasklist.list" /></label>
- <roundcube:object name="plugin.tasklist_select" id="taskedit-tasklist" tabindex="26" />
+ <roundcube:object name="plugin.tasklist_select" id="taskedit-tasklist" />
</div>
</div>
<!-- recurrence settings -->
@@ -84,7 +84,8 @@
<div id="taskedit-attachments">
<roundcube:object name="plugin.attachments_list" id="taskedit-attachment-list" class="attachmentslist" />
</div>
- <div id="taskedit-attachments-form">
+ <div id="taskedit-attachments-form" role="region" aria-labelledby="aria-label-attachmentuploadform">
+ <h3 id="aria-label-attachmentuploadform" class="voice"><roundcube:label name="arialabelattachmentuploadform" /></h2>
<roundcube:object name="plugin.attachments_form" id="taskedit-attachment-form" attachmentFieldSize="30" />
</div>
<roundcube:object name="plugin.filedroparea" id="taskedit-tab-2" />
diff --git a/plugins/tasklist/tasklist.js b/plugins/tasklist/tasklist.js
index 92685fa..fb7c4ac 100644
--- a/plugins/tasklist/tasklist.js
+++ b/plugins/tasklist/tasklist.js
@@ -80,6 +80,8 @@ function rcube_tasklist_ui(settings)
var scroll_sensitivity = 40;
var scroll_timer;
var tasklists_widget;
+ var focused_task;
+ var focused_subclass;
var me = this;
// general datepicker settings
@@ -140,6 +142,7 @@ function rcube_tasklist_ui(settings)
id_prefix: 'rcmlitasklist',
selectable: true,
save_state: true,
+ keyboard: false,
searchbox: '#tasklistsearch',
search_action: 'tasks/tasklist',
search_sources: [ 'folders', 'users' ],
@@ -168,9 +171,15 @@ function rcube_tasklist_ui(settings)
$(p.item).data('type', 'tasklist');
}
});
+ tasklists_widget.addEventListener('search-complete', function(data) {
+ if (data.length)
+ rcmail.display_message(rcmail.gettext('nrtasklistsfound','tasklist').replace('$nr', data.length), 'voice');
+ else
+ rcmail.display_message(rcmail.gettext('notasklistsfound','tasklist'), 'info');
+ });
// init (delegate) event handler on tasklist checkboxes
- tasklists_widget.container.on('click', 'input[type=checkbox]', function(e){
+ tasklists_widget.container.on('click', 'input[type=checkbox]', function(e) {
var list, id = this.value;
if ((list = me.tasklists[id])) {
list.active = this.checked;
@@ -186,6 +195,13 @@ function rcube_tasklist_ui(settings)
}
e.stopPropagation();
})
+ .on('keypress', 'input[type=checkbox]', function(e) {
+ // select tasklist on <Enter>
+ if (e.keyCode == 13) {
+ tasklists_widget.select(this.value);
+ return rcube_event.cancel(e);
+ }
+ })
.find('li:not(.virtual)').data('type', 'tasklist');
// handler for clicks on quickview buttons
@@ -250,7 +266,7 @@ function rcube_tasklist_ui(settings)
}).find('input[type=text]').placeholder(rcmail.gettext('createnewtask','tasklist'));
// click-handler on tags list
- $(rcmail.gui_objects.tagslist).click(function(e){
+ $(rcmail.gui_objects.tagslist).on('click', 'li', function(e){
var item = e.target.nodeName == 'LI' ? $(e.target) : $(e.target).closest('li'),
tag = item.data('value');
@@ -265,17 +281,17 @@ function rcube_tasklist_ui(settings)
if (tagsfilter.length > 1)
index = -1;
- $('li', this).removeClass('selected');
+ $('li', rcmail.gui_objects.tagslist).removeClass('selected').attr('aria-checked', 'false');
tagsfilter = [];
}
// add tag to filter
if (index < 0) {
- item.addClass('selected');
+ item.addClass('selected').attr('aria-checked', 'true');
tagsfilter.push(tag);
}
else if (shift) {
- item.removeClass('selected');
+ item.removeClass('selected').attr('aria-checked', 'false');
var a = tagsfilter.slice(0,index);
tagsfilter = a.concat(tagsfilter.slice(index+1));
}
@@ -289,6 +305,11 @@ function rcube_tasklist_ui(settings)
e.preventDefault();
return false;
})
+ .on('keypress', 'li', function(e) {
+ if (e.keyCode == 13) {
+ $(this).trigger('click', { pointerType:'keyboard' });
+ }
+ })
.mousedown(function(e){
// disable content selection with the mouse
e.preventDefault();
@@ -296,7 +317,7 @@ function rcube_tasklist_ui(settings)
});
// click-handler on task list items (delegate)
- $(rcmail.gui_objects.resultlist).click(function(e){
+ $(rcmail.gui_objects.resultlist).on('click', function(e){
var item = $(e.target);
var className = e.target.className;
@@ -314,11 +335,11 @@ function rcube_tasklist_ui(settings)
var id = item.data('id'),
li = item.parent(),
rec = listdata[id];
-
+
switch (className) {
case 'childtoggle':
rec.collapsed = !rec.collapsed;
- li.children('.childtasks:first').toggle();
+ li.children('.childtasks:first').toggle().attr('aria-hidden', rec.collapsed ? 'true' : 'false');
$(e.target).toggleClass('collapsed').html(rec.collapsed ? '▶' : '▼');
rcmail.http_post('tasks/task', { action:'collapse', t:{ id:rec.id, list:rec.list }, collapsed:rec.collapsed?1:0 });
if (e.shiftKey) // expand/collapse all childs
@@ -333,16 +354,16 @@ function rcube_tasklist_ui(settings)
li.toggleClass('complete');
save_task(rec, 'edit');
return true;
-
+
case 'flagged':
if (rcmail.busy)
return false;
rec.flagged = rec.flagged ? 0 : 1;
- li.toggleClass('flagged');
+ li.toggleClass('flagged').find('.flagged:first').attr('aria-checked', (rec.flagged ? 'true' : 'false'));
save_task(rec, 'edit');
break;
-
+
case 'date':
if (rcmail.busy)
return false;
@@ -364,7 +385,7 @@ function rcube_tasklist_ui(settings)
.datepicker('setDate', rec.date)
.datepicker('show');
break;
-
+
case 'delete':
delete_task(id);
break;
@@ -372,14 +393,12 @@ function rcube_tasklist_ui(settings)
case 'actions':
var pos, ref = $(e.target),
menu = $('#taskitemmenu');
+
if (menu.is(':visible') && menu.data('refid') == id) {
- menu.hide();
+ rcmail.command('menu-close', 'taskitemmenu');
}
else {
- pos = ref.offset();
- pos.left += ref.width() - menu.outerWidth();
- pos.top += (pos.top + ref.outerHeight() + menu.height() > $(window).height() ? -menu.height() : ref.outerHeight());
- menu.css({ top:pos.top+'px', left:pos.left+'px' }).show();
+ rcmail.command('menu-open', { menu: 'taskitemmenu', show: true }, e.target, e);
menu.data('refid', id);
me.selected_task = rec;
}
@@ -397,7 +416,7 @@ function rcube_tasklist_ui(settings)
return false;
})
- .dblclick(function(e){
+ .on('dblclick', '.taskhead, .childtoggle', function(e){
var id, rec, item = $(e.target);
if (!item.hasClass('taskhead'))
item = item.closest('div.taskhead');
@@ -410,6 +429,58 @@ function rcube_tasklist_ui(settings)
task_edit_dialog(id, 'edit');
clearSelection();
}
+ })
+ .on('keydown', '.taskhead', function(e) {
+ var inc = 1;
+ switch (e.keyCode) {
+ case 13: // Enter
+ $(e.target).trigger('click', { pointerType:'keyboard' });
+ return rcube_event.cancel(e);
+
+ case 38: // Up arrow key
+ inc = -1;
+ case 40: // Down arrow key
+ if ($(e.target).hasClass('actions')) {
+ // unfold actions menu
+ $(e.target).trigger('click', { pointerType:'keyboard' });
+ return rcube_event.cancel(e);
+ }
+
+ // focus next/prev task item
+ var x = 0, target = this, items = $(rcmail.gui_objects.resultlist).find('.taskhead:visible');
+ items.each(function(i, item) {
+ if (item === target) {
+ x = i;
+ return false;
+ }
+ });
+ items.get(x + inc).focus();
+ return rcube_event.cancel(e);
+
+ case 37: // Left arrow key
+ case 39: // Right arrow key
+ $(this).parent().children('.childtoggle:visible').first().trigger('click', { pointerType:'keyboard' });
+ break;
+ }
+ })
+ .on('focusin', '.taskhead', function(e){
+ if (rcube_event.is_keyboard(e)) {
+ var item = $(e.target);
+ if (!item.hasClass('taskhead'))
+ item = item.closest('div.taskhead');
+
+ var id = item.data('id');
+ if (id && listdata[id]) {
+ focused_task = id;
+ focused_subclass = item.get(0) !== e.target ? e.target.className : null;
+ }
+ }
+ })
+ .on('focusout', '.taskhead', function(e){
+ var item = $(e.target);
+ if (focused_task && item.data('id') == focused_task) {
+ focused_task = focused_subclass = null;
+ }
});
// handle global document clicks: close popup menus
@@ -506,8 +577,8 @@ function rcube_tasklist_ui(settings)
else
render_tasklist();
- $('#taskselector li.selected').removeClass('selected');
- $('#taskselector li.'+selector).addClass('selected');
+ $('#taskselector li.selected').removeClass('selected').attr('aria-checked', 'false');
+ $('#taskselector li.'+selector).addClass('selected').attr('aria-checked', 'true');
}
/**
@@ -601,8 +672,10 @@ function rcube_tasklist_ui(settings)
fix_tree_toggles();
update_tagcloud(activetags);
- if (!count)
+ if (!count) {
msgbox.html(rcmail.gettext('notasksfound','tasklist')).show();
+ rcmail.display_message(rcmail.gettext('notasksfound','tasklist'), 'voice');
+ }
}
/**
@@ -658,7 +731,9 @@ function rcube_tasklist_ui(settings)
// append new tags to tag cloud
$.each(newtags, function(i, tag){
- $('<li>').attr('rel', tag).data('value', tag)
+ $('<li role="checkbox" aria-checked="false" tabindex="0"></li>')
+ .attr('rel', tag)
+ .data('value', tag)
.html(Q(tag) + '<span class="count"></span>')
.appendTo(rcmail.gui_objects.tagslist)
.draggable({
@@ -879,15 +954,18 @@ function rcube_tasklist_ui(settings)
for (var j=0; rec.tags && j < rec.tags.length; j++)
tags_html += '<span class="tag">' + Q(rec.tags[j]) + '</span>';
+ var label_id = rcmail.html_identifier(rec.id) + '-title';
var div = $('<div>').addClass('taskhead').html(
'<div class="progressbar"><div class="progressvalue" style="width:' + (rec.complete * 100) + '%"></div></div>' +
- '<input type="checkbox" name="completed[]" value="1" class="complete" ' + (is_complete(rec) ? 'checked="checked" ' : '') + '/>' +
- '<span class="flagged"></span>' +
- '<span class="title">' + text2html(Q(rec.title)) + '</span>' +
+ '<input type="checkbox" name="completed[]" value="1" class="complete" aria-label="' + rcmail.gettext('complete','tasklist') + '" ' + (is_complete(rec) ? 'checked="checked" ' : '') + '/>' +
+ '<span class="flagged" role="checkbox" tabindex="0" aria-checked="' + (rec.flagged ? 'true' : 'false') + '" aria-label="' + rcmail.gettext('flagged','tasklist') + '"></span>' +
+ '<span class="title" id="' + label_id + '">' + text2html(Q(rec.title)) + '</span>' +
'<span class="tags">' + tags_html + '</span>' +
'<span class="date">' + Q(rec.date || rcmail.gettext('nodate','tasklist')) + '</span>' +
- '<a href="#" class="actions">V</a>'
+ '<a href="#" class="actions" aria-haspopup="true" aria-expanded="false">' + rcmail.gettext('taskactions','tasklist') + '</a>'
)
+ .attr('tabindex', '0')
+ .attr('aria-labelledby', label_id)
.data('id', rec.id)
.draggable({
revert: 'invalid',
@@ -917,12 +995,12 @@ function rcube_tasklist_ui(settings)
inplace = true;
}
else {
- li = $('<li>')
+ li = $('<li role="treeitem">')
.attr('rel', rec.id)
.addClass('taskitem')
.append((rec.collapsed ? '<span class="childtoggle collapsed">▶' : '<span class="childtoggle expanded">▼') + '</span>')
.append(div)
- .append('<ul class="childtasks" style="' + (rec.collapsed ? 'display:none' : '') + '"></ul>');
+ .append('<ul class="childtasks" role="group" style="' + (rec.collapsed ? 'display:none' : '') + '" aria-hidden="' + (rec.collapsed ? 'true' : 'false') +'"></ul>');
if (!parent || !parent.length)
li.appendTo(rcmail.gui_objects.resultlist);
@@ -935,6 +1013,11 @@ function rcube_tasklist_ui(settings)
resort_task(rec, li, true);
// TODO: remove the item after a while if it doesn't match the current filter anymore
}
+
+ // re-set focus to taskhead element after DOM update
+ if (focused_task == rec.id) {
+ focus_task(li);
+ }
}
/**
@@ -955,7 +1038,11 @@ function rcube_tasklist_ui(settings)
li.slideUp(speed, function(){
if (before) li.insertBefore(before);
else if (after) li.insertAfter(after);
- li.slideDown(speed);
+ li.slideDown(speed, function(){
+ if (focused_task == rec.id) {
+ focus_task(li);
+ }
+ });
});
}
@@ -1024,6 +1111,17 @@ function rcube_tasklist_ui(settings)
}
/**
+ * Set focus on the given task item after DOM update
+ */
+ function focus_task(li)
+ {
+ var selector = '.taskhead';
+ if (focused_subclass)
+ selector += ' .' + focused_subclass
+ li.find(selector).focus();
+ }
+
+ /**
* Determine whether the given task should be displayed as "complete"
*/
function is_complete(rec)
@@ -1346,7 +1444,7 @@ function rcube_tasklist_ui(settings)
$.each(typeof rec.tags == 'object' && rec.tags.length ? rec.tags : [''], function(i,val){
$('<input>')
.attr('name', 'tags[]')
- .attr('tabindex', '3')
+ .attr('tabindex', '0')
.addClass('tag')
.val(val)
.appendTo(tagline);
@@ -2032,8 +2130,10 @@ function rcube_tasklist_ui(settings)
*/
function set_focusview(id)
{
- if (focusview && focusview != id)
- $(tasklists_widget.get_item(focusview)).find('.tasklist').first().removeClass('focusview');
+ if (focusview && focusview != id) {
+ $(tasklists_widget.get_item(focusview)).find('.tasklist').first().removeClass('focusview')
+ .find('a.quickview').attr('aria-checked', 'false');
+ }
focusview = id;
@@ -2050,7 +2150,7 @@ function rcube_tasklist_ui(settings)
list_tasks(null);
if (focusview) {
- li.addClass('focusview');
+ li.addClass('focusview').find('a.quickview').attr('aria-checked', 'true');
}
}
diff --git a/plugins/tasklist/tasklist_ui.php b/plugins/tasklist/tasklist_ui.php
index 377fe68..fc7320e 100644
--- a/plugins/tasklist/tasklist_ui.php
+++ b/plugins/tasklist/tasklist_ui.php
@@ -187,12 +187,13 @@ class tasklist_ui
$classes[] = $prop['class'];
if (!$activeonly || $prop['active']) {
+ $label_id = 'tl:' . $id;
return html::div(join(' ', $classes),
- html::span(array('class' => 'listname', 'title' => $title), $prop['listname'] ?: $prop['name']) .
+ html::span(array('class' => 'listname', 'title' => $title, 'id' => $label_id), $prop['listname'] ?: $prop['name']) .
($prop['virtual'] ? '' :
- html::tag('input', array('type' => 'checkbox', 'name' => '_list[]', 'value' => $id, 'checked' => $prop['active'])) .
- html::span(array('class' => 'quickview', 'title' => $this->plugin->gettext('focusview')), ' ') .
- (isset($prop['subscribed']) ? html::a(array('href' => '#', 'class' => 'subscribed', 'title' => $this->plugin->gettext('tasklistsubscribe')), ' ') : '')
+ html::tag('input', array('type' => 'checkbox', 'name' => '_list[]', 'value' => $id, 'checked' => $prop['active'], 'aria-labelledby' => $label_id)) .
+ html::a(array('href' => '#', 'class' => 'quickview', 'title' => $this->plugin->gettext('focusview'), 'role' => 'checkbox', 'aria-checked' => 'false'), ' ') .
+ (isset($prop['subscribed']) ? html::a(array('href' => '#', 'class' => 'subscribed', 'title' => $this->plugin->gettext('tasklistsubscribe'), 'role' => 'checkbox', 'aria-checked' => $prop['subscribed'] ? 'true' : 'false'), ' ') : '')
)
);
}
@@ -276,11 +277,12 @@ class tasklist_ui
{
$attrib += array('action' => $this->rc->url('add'), 'method' => 'post', 'id' => 'quickaddform');
+ $label = html::label(array('for' => 'quickaddinput', 'class' => 'voice'), $this->plugin->gettext('quickaddinput'));
$input = new html_inputfield(array('name' => 'text', 'id' => 'quickaddinput'));
- $button = html::tag('input', array('type' => 'submit', 'value' => '+', 'class' => 'button mainaction'));
+ $button = html::tag('input', array('type' => 'submit', 'value' => '+', 'title' => $this->plugin->gettext('createtask'), 'class' => 'button mainaction'));
$this->rc->output->add_gui_object('quickaddform', $attrib['id']);
- return html::tag('form', $attrib, $input->show() . $button);
+ return html::tag('form', $attrib, $label . $input->show() . $button);
}
/**
@@ -317,6 +319,7 @@ class tasklist_ui
$this->rc->output->add_gui_object('edittagline', $attrib['id']);
$input = new html_inputfield(array('name' => 'tags[]', 'class' => 'tag', 'size' => $attrib['size'], 'tabindex' => $attrib['tabindex']));
+ unset($attrib['tabindex']);
return html::div($attrib, $input->show(''));
}
commit 55ac14b5f3aaf8822e1c97b1be103376a5bc4f61
Merge: 0e720bd 3acc91b
Author: Thomas Bruederli <bruederli at kolabsys.com>
Date: Fri Jun 20 15:07:57 2014 +0200
Merge branch 'master' of ssh://git.kolab.org/git/roundcubemail-plugins-kolab
commit 0e720bd7ecb9385a3a87871e4cae04ef6281e52f
Author: Thomas Bruederli <bruederli at kolabsys.com>
Date: Fri Jun 20 13:20:21 2014 +0200
Add keyboard navigation and basic accessibility improvements to the tagedit widget
diff --git a/plugins/tasklist/jquery.tagedit.js b/plugins/tasklist/jquery.tagedit.js
index efb4b74..baab701 100644
--- a/plugins/tasklist/jquery.tagedit.js
+++ b/plugins/tasklist/jquery.tagedit.js
@@ -11,7 +11,7 @@
* JavaScript code in this file.
*
* Copyright (c) 2010 Oliver Albrecht <info at webwork-albrecht.de>
-* Copyright (c) 2012 Thomas Brüderli <thomas at roundcube.net>
+* Copyright (c) 2014 Thomas Brüderli <thomas at roundcube.net>
*
* Licensed under the MIT licenses
*
@@ -38,7 +38,7 @@
* for the JavaScript code in this file.
*
* @author Oliver Albrecht Mial: info at webwork-albrecht.de Twitter: @webworka
-* @version 1.5.1 (10/2013)
+* @version 1.5.2 (06/2014)
* Requires: jQuery v1.4+, jQueryUI v1.8+, jQuerry.autoGrowInput
*
* Example of usage:
@@ -125,6 +125,7 @@
}
var elements = this;
+ var focusItem = null;
var baseNameRegexp = new RegExp("^(.*)\\[([0-9]*?("+options.deletedPostfix+"|"+options.addedPostfix+")?)?\]$", "i");
@@ -153,17 +154,18 @@
function inputsToList() {
var html = '<ul class="tagedit-list '+options.additionalListClass+'">';
- elements.each(function() {
+ elements.each(function(i) {
var element_name = $(this).attr('name').match(baseNameRegexp);
if(element_name && element_name.length == 4 && (options.deleteEmptyItems == false || $(this).val().length > 0)) {
if(element_name[1].length > 0) {
- var elementId = typeof element_name[2] != 'undefined'? element_name[2]: '';
+ var elementId = typeof element_name[2] != 'undefined'? element_name[2]: '',
+ domId = 'tagedit-' + baseName + '-' + (elementId || i);
- html += '<li class="tagedit-listelement tagedit-listelement-old">';
- html += '<span dir="'+options.direction+'">' + $(this).val() + '</span>';
+ html += '<li class="tagedit-listelement tagedit-listelement-old" aria-labelledby="'+domId+'">';
+ html += '<span dir="'+options.direction+'" id="'+domId+'">' + $(this).val() + '</span>';
html += '<input type="hidden" name="'+baseName+'['+elementId+']" value="'+$(this).val()+'" />';
if (options.allowDelete)
- html += '<a class="tagedit-close" title="'+options.texts.removeLinkTitle+'">x</a>';
+ html += '<a class="tagedit-close" title="'+options.texts.removeLinkTitle+'" aria-label="'+options.texts.removeLinkTitle+' '+$(this).val()+'">x</a>';
html += '</li>';
}
}
@@ -215,12 +217,13 @@
}
if(options.allowAdd == true || oldValue) {
+ var domId = 'tagedit-' + baseName + '-' + id;
// Make a new tag in front the input
- html = '<li class="tagedit-listelement tagedit-listelement-old">';
- html += '<span dir="'+options.direction+'">' + $(this).val() + '</span>';
+ html = '<li class="tagedit-listelement tagedit-listelement-old" aria-labelledby="'+domId+'">';
+ html += '<span dir="'+options.direction+'" id="'+domId+'">' + $(this).val() + '</span>';
var name = oldValue? baseName + '['+id+options.addedPostfix+']' : baseName + '[]';
html += '<input type="hidden" name="'+name+'" value="'+$(this).val()+'" />';
- html += '<a class="tagedit-close" title="'+options.texts.removeLinkTitle+'">x</a>';
+ html += '<a class="tagedit-close" title="'+options.texts.removeLinkTitle+'" aria-label="'+options.texts.removeLinkTitle+' '+$(this).val()+'">x</a>';
html += '</li>';
$(this).parent().before(html);
@@ -239,8 +242,19 @@
var code = event.keyCode > 0? event.keyCode : event.which;
switch(code) {
+ case 46:
+ if (!focusItem)
+ break;
case 8: // BACKSPACE
- if($(this).val().length == 0) {
+ if(focusItem) {
+ focusItem.fadeOut(options.animSpeed, function() {
+ $(this).remove();
+ })
+ unfocusItem();
+ event.preventDefault();
+ return false;
+ }
+ else if($(this).val().length == 0) {
// delete Last Tag
var elementToRemove = elements.find('li.tagedit-listelement-old').last();
elementToRemove.fadeOut(options.animSpeed, function() {elementToRemove.remove();})
@@ -254,7 +268,41 @@
event.preventDefault();
return false;
}
- break;
+ break;
+ case 37: // LEFT
+ case 39: // RIGHT
+ if($(this).val().length == 0) {
+ // select previous Tag
+ var inc = code == 37 ? -1 : 1,
+ items = elements.find('li.tagedit-listelement-old')
+ x = items.length, next = 0;
+ items.each(function(i, elem) {
+ if ($(elem).hasClass('tagedit-listelement-focus')) {
+ x = i;
+ return true;
+ }
+ });
+ unfocusItem();
+ next = Math.max(0, x + inc);
+ if (items.get(next)) {
+ focusItem = items.eq(next).addClass('tagedit-listelement-focus');
+ $(this).attr('aria-activedescendant', focusItem.attr('aria-labelledby'))
+
+ if(options.autocompleteOptions.source != false) {
+ $(this).autocomplete('close').autocomplete('disable');
+ }
+ }
+ event.preventDefault();
+ return false;
+ }
+ break;
+ default:
+ // ignore input if an item is focused
+ if (focusItem !== null) {
+ event.preventDefault();
+ event.bubble = false;
+ return false;
+ }
}
return true;
})
@@ -264,8 +312,11 @@
if($(this).val().length > 0 && $('ul.ui-autocomplete #ui-active-menuitem').length == 0) {
$(this).trigger('transformToTag');
}
- event.preventDefault();
- return false;
+ event.preventDefault();
+ return false;
+ }
+ else if($(this).val().length > 0){
+ unfocusItem();
}
return true;
})
@@ -287,9 +338,15 @@
var input = $(this);
$(this).data('blurtimer', window.setTimeout(function() {input.val('');}, 500));
}
+ unfocusItem();
+ // restore tabindex when widget looses focus
+ if (options.tabindex)
+ elements.attr('tabindex', options.tabindex);
})
.focus(function() {
window.clearTimeout($(this).data('blurtimer'));
+ // remove tabindex on <ul> because #tagedit-input now has it
+ elements.attr('tabindex', '-1');
});
if(options.autocompleteOptions.source != false) {
@@ -302,6 +359,7 @@
case 'A':
$(event.target).parent().fadeOut(options.animSpeed, function() {
$(event.target).parent().remove();
+ elements.find('#tagedit-input').focus();
});
break;
case 'INPUT':
@@ -325,6 +383,20 @@
}
/**
+ * Remove class and reference to currently focused tag item
+ */
+ function unfocusItem() {
+ if(focusItem){
+ if(options.autocompleteOptions.source != false) {
+ elements.find('#tagedit-input').autocomplete('enable');
+ }
+ focusItem.removeClass('tagedit-listelement-focus');
+ focusItem = null;
+ elements.find('#tagedit-input').removeAttr('aria-activedescendant');
+ }
+ }
+
+ /**
* Sets all Actions and events for editing an Existing Tag.
*
* @param event {object} The original Event that was given
commit 89ece32c78ad5c813b7c8966257585640d02e276
Author: Thomas Bruederli <bruederli at kolabsys.com>
Date: Thu Jun 19 17:45:39 2014 +0200
Fix calendar edit form labels and item selection
diff --git a/plugins/calendar/calendar_ui.js b/plugins/calendar/calendar_ui.js
index ff9e5b4..3558d91 100644
--- a/plugins/calendar/calendar_ui.js
+++ b/plugins/calendar/calendar_ui.js
@@ -2832,6 +2832,13 @@ function rcube_calendar_ui(settings)
e.stopPropagation();
}
+ })
+ .on('keypress', 'input[type=checkbox]', function(e) {
+ // select calendar on <Enter>
+ if (e.keyCode == 13) {
+ calendars_list.select(this.value);
+ return rcube_event.cancel(e);
+ }
});
// register dbl-click handler to open calendar edit dialog
diff --git a/plugins/calendar/drivers/kolab/kolab_driver.php b/plugins/calendar/drivers/kolab/kolab_driver.php
index 457b83e..ca14d89 100644
--- a/plugins/calendar/drivers/kolab/kolab_driver.php
+++ b/plugins/calendar/drivers/kolab/kolab_driver.php
@@ -1282,8 +1282,9 @@ class kolab_driver extends calendar_driver
$hidden_fields[] = array('name' => 'parent', 'value' => $path_imap);
}
else {
- $select = kolab_storage::folder_selector('event', array('name' => 'parent'), $folder);
+ $select = kolab_storage::folder_selector('event', array('name' => 'parent', 'id' => 'calendar-parent'), $folder);
$form['props']['fieldsets']['location']['content']['path'] = array(
+ 'id' => 'calendar-parent',
'label' => $this->cal->gettext('parentcalendar'),
'value' => $select->show(strlen($folder) ? $path_imap : ''),
);
@@ -1363,10 +1364,9 @@ class kolab_driver extends calendar_driver
if (is_array($form['content']) && !empty($form['content'])) {
$table = new html_table(array('cols' => 2));
foreach ($form['content'] as $col => $colprop) {
- $colprop['id'] = '_'.$col;
$label = !empty($colprop['label']) ? $colprop['label'] : rcube_label($col);
- $table->add('title', sprintf('<label for="%s">%s</label>', $colprop['id'], Q($label)));
+ $table->add('title', html::label($colprop['id'], Q($label)));
$table->add(null, $colprop['value']);
}
$content = $table->show();
diff --git a/plugins/calendar/skins/larry/calendar.css b/plugins/calendar/skins/larry/calendar.css
index 84169fd..c274174 100644
--- a/plugins/calendar/skins/larry/calendar.css
+++ b/plugins/calendar/skins/larry/calendar.css
@@ -1174,10 +1174,14 @@ a.dropdown-link:after {
padding-right: 10px;
}
-#calendar-kolabform fieldset.tab {
+#calendar-kolabform .tabsbar.ui-tabs-nav {
+ margin-bottom: 0;
+}
+
+#calendar-kolabform fieldset.ui-tabs-panel {
background: #efefef;
display: block;
- margin-top: 0.5em;
+ margin-top: 2px;
padding: 0.5em 1em;
}
diff --git a/plugins/calendar/skins/larry/templates/calendar.html b/plugins/calendar/skins/larry/templates/calendar.html
index 6fce4ca..6e7ed49 100644
--- a/plugins/calendar/skins/larry/templates/calendar.html
+++ b/plugins/calendar/skins/larry/templates/calendar.html
@@ -42,7 +42,7 @@
<roundcube:object name="plugin.calendar_list" id="calendarslist" class="treelist listing" />
</div>
<div class="boxfooter">
- <roundcube:button command="calendar-create" type="link" title="calendar.createcalendar" class="listbutton add disabled" classAct="listbutton add" innerClass="inner" content="+" /><roundcube:button name="calendaroptionslink" id="calendaroptionsmenulink" type="link" title="moreactions" class="listbutton groupactions" onclick="return UI.toggle_popup('calendaroptionsmenu', event, { above:true })" innerClass="inner" label="calendar.calendaractions" aria-haspopup="true" aria-expanded="false" aria-owns="calendaroptionsmenu-menu" />
+ <roundcube:button command="calendar-create" type="link" title="calendar.createcalendar" class="listbutton add disabled" classAct="listbutton add" innerClass="inner" content="+" /><roundcube:button name="calendaroptionslink" id="calendaroptionsmenulink" type="link" title="moreactions" class="listbutton groupactions" onclick="return UI.toggle_popup('calendaroptionsmenu', event, { above:true })" innerClass="inner" label="calendar.calendaractions" aria-haspopup="true" aria-expanded="false" aria-owns="calendaroptionsmenu-menu" />
</div>
</div>
</div>
commit e7679b10142ad9c47c3dd3c6033882062bcde308
Author: Thomas Bruederli <bruederli at kolabsys.com>
Date: Thu Jun 19 16:44:56 2014 +0200
Link text labels with recurrence form elements
diff --git a/plugins/libcalendaring/libcalendaring.php b/plugins/libcalendaring/libcalendaring.php
index b84ede7..c157c6a 100644
--- a/plugins/libcalendaring/libcalendaring.php
+++ b/plugins/libcalendaring/libcalendaring.php
@@ -735,19 +735,19 @@ class libcalendaring extends rcube_plugin
$select->add($this->gettext('monthly'), 'MONTHLY');
$select->add($this->gettext('yearly'), 'YEARLY');
$select->add($this->gettext('rdate'), 'RDATE');
- $html = html::label('edit-frequency', $this->gettext('frequency')) . $select->show('');
+ $html = html::label('edit-recurrence-frequency', $this->gettext('frequency')) . $select->show('');
break;
// daily recurrence
case 'daily':
$select = $this->interval_selector(array('name' => 'interval', 'class' => 'edit-recurrence-interval', 'id' => 'edit-recurrence-interval-daily'));
- $html = html::div($attrib, html::label(null, $this->gettext('every')) . $select->show(1) . html::span('label-after', $this->gettext('days')));
+ $html = html::div($attrib, html::label('edit-recurrence-interval-daily', $this->gettext('every')) . $select->show(1) . html::span('label-after', $this->gettext('days')));
break;
// weekly recurrence form
case 'weekly':
$select = $this->interval_selector(array('name' => 'interval', 'class' => 'edit-recurrence-interval', 'id' => 'edit-recurrence-interval-weekly'));
- $html = html::div($attrib, html::label(null, $this->gettext('every')) . $select->show(1) . html::span('label-after', $this->gettext('weeks')));
+ $html = html::div($attrib, html::label('edit-recurrence-interval-weekly', $this->gettext('every')) . $select->show(1) . html::span('label-after', $this->gettext('weeks')));
// weekday selection
$daymap = array('sun','mon','tue','wed','thu','fri','sat');
$checkbox = new html_checkbox(array('name' => 'byday', 'class' => 'edit-recurrence-weekly-byday'));
@@ -765,7 +765,7 @@ class libcalendaring extends rcube_plugin
// monthly recurrence form
case 'monthly':
$select = $this->interval_selector(array('name' => 'interval', 'class' => 'edit-recurrence-interval', 'id' => 'edit-recurrence-interval-monthly'));
- $html = html::div($attrib, html::label(null, $this->gettext('every')) . $select->show(1) . html::span('label-after', $this->gettext('months')));
+ $html = html::div($attrib, html::label('edit-recurrence-interval-monthly', $this->gettext('every')) . $select->show(1) . html::span('label-after', $this->gettext('months')));
$checkbox = new html_checkbox(array('name' => 'bymonthday', 'class' => 'edit-recurrence-monthly-bymonthday'));
for ($monthdays = '', $d = 1; $d <= 31; $d++) {
@@ -787,7 +787,7 @@ class libcalendaring extends rcube_plugin
// annually recurrence form
case 'yearly':
$select = $this->interval_selector(array('name' => 'interval', 'class' => 'edit-recurrence-interval', 'id' => 'edit-recurrence-interval-yearly'));
- $html = html::div($attrib, html::label(null, $this->gettext('every')) . $select->show(1) . html::span('label-after', $this->gettext('years')));
+ $html = html::div($attrib, html::label('edit-recurrence-interval-yearly', $this->gettext('every')) . $select->show(1) . html::span('label-after', $this->gettext('years')));
// month selector
$monthmap = array('','jan','feb','mar','apr','may','jun','jul','aug','sep','oct','nov','dec');
$checkbox = new html_checkbox(array('name' => 'bymonth', 'class' => 'edit-recurrence-yearly-bymonth'));
@@ -812,17 +812,18 @@ class libcalendaring extends rcube_plugin
$this->gettext('forever'))
);
+ $forntimes = $this->gettext(array(
+ 'name' => 'forntimes',
+ 'vars' => array('nr' => '%s'))
+ );
$html .= html::div('line',
- $radio->show('', array('value' => 'count', 'id' => 'edit-recurrence-repeat-count')) . ' ' .
- $this->gettext(array(
- 'name' => 'forntimes',
- 'vars' => array('nr' => $select->show(1)))
- )
+ $radio->show('', array('value' => 'count', 'id' => 'edit-recurrence-repeat-count', 'aria-label' => sprintf($forntimes, 'N'))) . ' ' .
+ sprintf($forntimes, $select->show(1))
);
$html .= html::div('line',
- $radio->show('', array('value' => 'until', 'id' => 'edit-recurrence-repeat-until')) . ' ' .
- $this->gettext('untildate') . ' ' . $input->show('')
+ $radio->show('', array('value' => 'until', 'id' => 'edit-recurrence-repeat-until', 'aria-label' => $this->gettext('untilenddate'))) . ' ' .
+ $this->gettext('untildate') . ' ' . $input->show('', array('aria-label' => $this->gettext('untilenddate')))
);
$html = html::div($attrib, html::label(null, ucfirst($this->gettext('recurrencend'))) . $html);
diff --git a/plugins/libcalendaring/localization/en_US.inc b/plugins/libcalendaring/localization/en_US.inc
index 863b7fa..d89833c 100644
--- a/plugins/libcalendaring/localization/en_US.inc
+++ b/plugins/libcalendaring/localization/en_US.inc
@@ -57,6 +57,7 @@ $labels['onevery'] = 'On every';
$labels['onsamedate'] = 'On the same date';
$labels['forever'] = 'forever';
$labels['recurrencend'] = 'until';
+$labels['untilenddate'] = 'until date';
$labels['forntimes'] = 'for $nr time(s)';
$labels['first'] = 'first';
$labels['second'] = 'second';
More information about the commits
mailing list