MDL-41392 course: management accessibility tweaking

This commit is contained in:
Sam Hemelryk 2013-11-11 17:09:44 +08:00
parent 68bcc3faaf
commit d0647301a0
16 changed files with 386 additions and 81 deletions

View File

@ -140,7 +140,8 @@ switch ($action) {
$categoryid = required_param('categoryid', PARAM_INT);
/* @var core_course_management_renderer $renderer */
$renderer = $PAGE->get_renderer('core_course', 'management');
$outcome->html = html_writer::start_tag('ul', array('class' => 'ml'));
$outcome->html = html_writer::start_tag('ul',
array('class' => 'ml', 'role' => 'group', 'id' => 'subcategoriesof'.$categoryid));
$coursecat = coursecat::get($categoryid);
foreach ($coursecat->get_children() as $subcat) {
$outcome->html .= $renderer->category_listitem($subcat, array(), $subcat->get_children_count());

View File

@ -44,7 +44,19 @@ class core_course_management_renderer extends plugin_renderer_base {
public function enhance_management_interface() {
$this->page->requires->yui_module('moodle-course-management', 'M.course.management.init');
$this->page->requires->strings_for_js(
array('show', 'hide', 'expand', 'collapse', 'confirmcoursemove', 'move', 'cancel', 'confirm'),
array(
'show',
'showcategory',
'hide',
'expand',
'expandcategory',
'collapse',
'collapsecategory',
'confirmcoursemove',
'move',
'cancel',
'confirm'
),
'moodle'
);
}
@ -63,6 +75,8 @@ class core_course_management_renderer extends plugin_renderer_base {
$html .= $this->heading($heading);
}
if ($viewmode !== null) {
$html .= html_writer::start_div();
$html .= $this->view_mode_selector(\core_course\management\helper::get_management_viewmodes(), $viewmode);
if ($viewmode === 'courses') {
$categories = coursecat::make_categories_list(array('moodle/category:manage', 'moodle/course:create'));
$nothing = false;
@ -73,7 +87,7 @@ class core_course_management_renderer extends plugin_renderer_base {
$select = new single_select($this->page->url, 'categoryid', $categories, $categoryid, $nothing);
$html .= $this->render($select);
}
$html .= $this->view_mode_selector(\core_course\management\helper::get_management_viewmodes(), $viewmode);
$html .= html_writer::end_div();
}
$html .= html_writer::end_div();
return $html;
@ -183,8 +197,14 @@ class core_course_management_renderer extends plugin_renderer_base {
'aria-expanded' => $isexpanded ? 'true' : 'false'
);
$text = $category->get_formatted_name();
if ($category->parent) {
$a = new stdClass;
$a->category = $text;
$a->parentcategory = $category->get_parent_coursecat()->get_formatted_name();
$textlabel = get_string('categorysubcategoryof', 'moodle', $a);
}
$courseicon = $this->output->pix_icon('i/course', get_string('courses'));
$bcatinput = array('type' => 'checkbox', 'name' => 'bcat[]', 'value' => $category->id, 'class' => 'bulk-action-checkbox');
$bcatinput = array('type' => 'checkbox', 'name' => 'bcat[]', 'value' => $category->id, 'class' => 'bulk-action-checkbox', 'aria-label' => $text);
if (!$category->can_resort_subcategories() && !$category->has_manage_capability()) {
// Very very hardcoded here.
@ -193,14 +213,36 @@ class core_course_management_renderer extends plugin_renderer_base {
$viewcaturl = new moodle_url('/course/management.php', array('categoryid' => $category->id));
if ($isexpanded) {
$icon = $this->output->pix_icon('t/switch_minus', get_string('collapse'), 'moodle', array('class' => 'tree-icon'));
$icon = html_writer::link($viewcaturl, $icon, array('class' => 'float-left', 'data-action' => 'collapse'));
$icon = $this->output->pix_icon('t/switch_minus', get_string('collapse'), 'moodle', array('class' => 'tree-icon', 'title' => ''));
$icon = html_writer::link(
$viewcaturl,
$icon,
array(
'class' => 'float-left',
'data-action' => 'collapse',
'title' => get_string('collapsecategory', 'moodle', $text),
'aria-controls' => 'subcategoryof'.$category->id
)
);
} else if ($isexpandable) {
$icon = $this->output->pix_icon('t/switch_plus', get_string('expand'), 'moodle', array('class' => 'tree-icon'));
$icon = html_writer::link($viewcaturl, $icon, array('class' => 'float-left', 'data-action' => 'expand'));
$icon = $this->output->pix_icon('t/switch_plus', get_string('expand'), 'moodle', array('class' => 'tree-icon', 'title' => ''));
$icon = html_writer::link(
$viewcaturl,
$icon,
array(
'class' => 'float-left',
'data-action' => 'expand',
'title' => get_string('expandcategory', 'moodle', $text)
)
);
} else {
$icon = $this->output->pix_icon('i/navigationitem', '', 'moodle', array('class' => 'tree-icon'));
$icon = html_writer::link($viewcaturl, $icon, array('class' => 'float-left'));
$icon = $this->output->pix_icon(
'i/navigationitem',
'',
'moodle',
array('class' => 'tree-icon', 'title' => get_string('showcategory', 'moodle', $text))
);
$icon = html_writer::span($icon, 'float-left');
}
$actions = \core_course\management\helper::get_category_listitem_actions($category);
$hasactions = !empty($actions) || $category->can_create_course();
@ -212,10 +254,14 @@ class core_course_management_renderer extends plugin_renderer_base {
$html .= html_writer::end_div();
$html .= $icon;
if ($hasactions) {
$html .= html_writer::link($viewcaturl, $text, array('class' => 'float-left categoryname'));
$textattributes = array('class' => 'float-left categoryname');
} else {
$html .= html_writer::link($viewcaturl, $text, array('class' => 'float-left categoryname without-actions'));
$textattributes = array('class' => 'float-left categoryname without-actions');
}
if (isset($textlabel)) {
$textattributes['aria-label'] = $textlabel;
}
$html .= html_writer::link($viewcaturl, $text, $textattributes);
$html .= html_writer::start_div('float-right');
if ($category->idnumber) {
$html .= html_writer::tag('span', s($category->idnumber), array('class' => 'dimmed idnumber'));
@ -224,16 +270,18 @@ class core_course_management_renderer extends plugin_renderer_base {
$html .= $this->category_listitem_actions($category, $actions);
}
$countid = 'course-count-'.$category->id;
$html .= html_writer::span(get_string('courses'), 'accesshide', array('id' => $countid));
$html .= html_writer::span(
html_writer::span($category->get_courses_count()).$courseicon,
html_writer::span($category->get_courses_count()) .
html_writer::span(get_string('courses'), 'accesshide', array('id' => $countid)) .
$courseicon,
'course-count dimmed',
array('aria-labelledby' => $countid)
);
$html .= html_writer::end_div();
$html .= html_writer::end_div();
if ($isexpanded) {
$html .= html_writer::start_tag('ul', array('class' => 'ml', 'role' => 'group'));
$html .= html_writer::start_tag('ul',
array('class' => 'ml', 'role' => 'group', 'id' => 'subcategoryof'.$category->id));
$catatlevel = \core_course\management\helper::get_expanded_categories($category->path);
$catatlevel[] = array_shift($selectedcategories);
$catatlevel = array_unique($catatlevel);
@ -341,7 +389,8 @@ class core_course_management_renderer extends plugin_renderer_base {
$selectoptions,
'selectsortby',
'selectedcategories',
false
false,
array('aria-label' => get_string('selectcategorysort'))
)
);
$form .= html_writer::div(
@ -353,7 +402,8 @@ class core_course_management_renderer extends plugin_renderer_base {
),
'resortcategoriesby',
'name',
false
false,
array('aria-label' => get_string('selectcategorysortby'))
)
);
$form .= html_writer::div(
@ -366,15 +416,17 @@ class core_course_management_renderer extends plugin_renderer_base {
),
'resortcoursesby',
'fullname',
false
false,
array('aria-label' => get_string('selectcoursesortby'))
)
);
$form .= html_writer::empty_tag('input', array('type' => 'submit', 'name' => 'bulksort', 'value' => get_string('sort')));
$form .= html_writer::end_div();
$html .= $this->detail_pair(
get_string('sorting'),
$form
);
$html .= html_writer::start_div('detail-pair row yui3-g');
$html .= html_writer::div(html_writer::span(get_string('sorting')), 'pair-key span3 yui3-u-1-4');
$html .= html_writer::div($form, 'pair-value span9 yui3-u-3-4');
$html .= html_writer::end_div();
}
if (coursecat::can_change_parent_any()) {
$options = array();
@ -412,7 +464,8 @@ class core_course_management_renderer extends plugin_renderer_base {
if ($category === null) {
$html = html_writer::start_div('select-a-category');
$html .= html_writer::tag('h3', get_string('courses'));
$html .= html_writer::tag('h3', get_string('courses'),
array('id' => 'course-listing-title', 'tabindex' => '0'));
$html .= $this->output->notification(get_string('selectacategory'), 'notifymessage');
$html .= html_writer::end_div();
return $html;
@ -445,10 +498,11 @@ class core_course_management_renderer extends plugin_renderer_base {
'data-totalcourses' => $totalcourses,
'data-canmoveoutof' => $category->can_move_courses_out_of() && $category->can_move_courses_into()
));
$html .= html_writer::tag('h3', $category->get_formatted_name());
$html .= html_writer::tag('h3', $category->get_formatted_name(),
array('id' => 'course-listing-title', 'tabindex' => '0'));
$html .= $this->course_listing_actions($category, $course, $perpage);
$html .= $this->listing_pagination($category, $page, $perpage);
$html .= html_writer::start_tag('ul', array('class' => 'ml'));
$html .= html_writer::start_tag('ul', array('class' => 'ml', 'role' => 'group'));
foreach ($category->get_courses($options) as $listitem) {
$html .= $this->course_listitem($category, $listitem, $courseid);
}
@ -546,7 +600,13 @@ class core_course_management_renderer extends plugin_renderer_base {
'data-visible' => $course->visible ? '1' : '0'
);
$bulkcourseinput = array('type' => 'checkbox', 'name' => 'bc[]', 'value' => $course->id, 'class' => 'bulk-action-checkbox');
$bulkcourseinput = array(
'type' => 'checkbox',
'name' => 'bc[]',
'value' => $course->id,
'class' => 'bulk-action-checkbox',
'aria-label' => $text
);
if (!$category->has_manage_capability()) {
// Very very hardcoded here.
$bulkcourseinput['style'] = 'visibility:hidden';
@ -691,7 +751,7 @@ class core_course_management_renderer extends plugin_renderer_base {
$fullname = $details['fullname']['value'];
$html = html_writer::start_div('course-detail');
$html .= html_writer::tag('h3', $fullname);
$html .= html_writer::tag('h3', $fullname, array('id' => 'course-detail-title', 'tabindex' => '0'));
$html .= $this->course_detail_actions($course);
foreach ($details as $class => $data) {
$html .= $this->detail_pair($data['key'], $data['value'], $class);
@ -741,12 +801,15 @@ class core_course_management_renderer extends plugin_renderer_base {
* @param string $text The text for the button.
* @param string $id An id to give the button.
* @param string $class A class to give the button.
* @param array $attributes Any additional attributes
* @return string
*/
protected function action_button(moodle_url $url, $text, $id = null, $class = null, $title = null) {
$attributes = array(
'class' => 'yui3-button',
);
protected function action_button(moodle_url $url, $text, $id = null, $class = null, $title = null, array $attributes = array()) {
if (isset($attributes['class'])) {
$attributes['class'] .= ' yui3-button';
} else {
$attributes['class'] = 'yui3-button';
}
if (!is_null($id)) {
$attributes['id'] = $id;
}
@ -757,6 +820,9 @@ class core_course_management_renderer extends plugin_renderer_base {
$title = $text;
}
$attributes['title'] = $title;
if (!isset($attributes['role'])) {
$attributes['role'] = 'button';
}
return html_writer::link($url, $text, $attributes);
}
@ -1124,4 +1190,31 @@ class core_course_management_renderer extends plugin_renderer_base {
return html_writer::span(join('', $actions), 'course-item-actions item-actions');
}
/**
* Creates access hidden skip to links for the displayed sections.
*
* @param bool $displaycategorylisting
* @param bool $displaycourselisting
* @param bool $displaycoursedetail
* @return string
*/
public function accessible_skipto_links($displaycategorylisting, $displaycourselisting, $displaycoursedetail) {
$html = html_writer::start_div('skiplinks accesshide');
$url = new moodle_url($this->page->url);
if ($displaycategorylisting) {
$url->set_anchor('category-listing');
$html .= html_writer::link($url, get_string('skiptocategorylisting'), array('class' => 'skip'));
}
if ($displaycategorylisting) {
$url->set_anchor('course-listing');
$html .= html_writer::link($url, get_string('skiptocourselisting'), array('class' => 'skip'));
}
if ($displaycategorylisting) {
$url->set_anchor('course-detail');
$html .= html_writer::link($url, get_string('skiptocoursedetails'), array('class' => 'skip'));
}
$html .= html_writer::end_div();
return $html;
}
}

View File

@ -459,6 +459,10 @@ if (($viewmode === 'default' || $viewmode === 'combined' || $viewmode === 'cours
$renderer = $PAGE->get_renderer('core_course', 'management');
$renderer->enhance_management_interface();
$displaycategorylisting = ($viewmode === 'default' || $viewmode === 'combined' || $viewmode === 'categories');
$displaycourselisting = ($viewmode === 'default' || $viewmode === 'combined' || $viewmode === 'courses');
$displaycoursedetail = (isset($courseid));
echo $renderer->header();
if (!$issearching) {
@ -478,12 +482,15 @@ if (count($notificationsfail) > 0) {
echo $renderer->management_form_start();
echo $renderer->grid_start('course-category-listings', $class);
if ($viewmode === 'default' || $viewmode === 'combined' || $viewmode === 'categories') {
echo $renderer->accessible_skipto_links($displaycategorylisting, $displaycourselisting, $displaycoursedetail);
if ($displaycategorylisting) {
echo $renderer->grid_column_start($categorysize, 'category-listing');
echo $renderer->category_listing($category);
echo $renderer->grid_column_end();
}
if ($viewmode === 'default' || $viewmode === 'combined' || $viewmode === 'courses') {
if ($displaycourselisting) {
echo $renderer->grid_column_start($coursesize, 'course-listing');
if (!$issearching) {
echo $renderer->course_listing($category, $course, $page, $perpage);
@ -493,7 +500,7 @@ if ($viewmode === 'default' || $viewmode === 'combined' || $viewmode === 'course
echo $renderer->search_listing($courses, $coursestotal, $course, $page, $perpage);
}
echo $renderer->grid_column_end();
if (isset($courseid)) {
if ($displaycoursedetail) {
echo $renderer->grid_column_start($detailssize, 'course-detail');
echo $renderer->course_detail($course);
echo $renderer->grid_column_end();

View File

@ -1394,4 +1394,15 @@ class behat_course extends behat_base {
}
$actionnode->click();
}
/**
* Clicks on a category in the management interface.
*
* @Given /^I click on "([^"]*)" category in the management category listing$/
* @param string $name The name of the category to click.
*/
public function i_click_on_category_in_the_management_category_listing($name) {
$node = $this->get_management_category_listing_node_by_name($name);
$node->find('css', 'a.categoryname')->click();
}
}

View File

@ -92,7 +92,7 @@ Feature: Test we can resort categories in the management interface.
And I log in as "admin"
And I go to the courses management page
And I should see the "Course categories" management page
And I click on "Master cat" "link"
And I click on "Master cat" category in the management category listing
# Redirect.
And I should see the "Course categories and courses" management page
And I click on <sortby> action for "Master cat" in management category listing

View File

@ -270,7 +270,7 @@ Feature: Course category management interface performs as expected
And I log in as "admin"
And I go to the courses management page
And I should see the "Course categories" management page
And I click on "Master cat" "link"
And I click on "Master cat" category in the management category listing
# Redirect.
And I should see the "Course categories and courses" management page
And I click on <sortby> action for "Master cat" in management category listing
@ -727,7 +727,7 @@ Feature: Course category management interface performs as expected
And I should see "Cat 2-1-2" in the "#course-category-listings ul.ml" "css_element"
And I should not see "Cat 2-1-1-1" in the "#course-category-listings ul.ml" "css_element"
And I should see "Cat 2-1-2-1" in the "#course-category-listings ul.ml" "css_element"
And I click on "Cat 1" "link"
And I click on "Cat 1" category in the management category listing
# Redirect.
And I should see the "Course categories and courses" management page
And I should see "Cat 1" in the "#course-category-listings ul.ml" "css_element"

View File

@ -841,6 +841,17 @@ Item.prototype = {
nodeup.insert(previousdown, 'after');
}
}
nodeup = node.one(' > div a.action-moveup');
if (nodeup) {
// Try to re-focus on up.
nodeup.focus();
} else {
// If we can't focus up we're at the bottom, try to focus on up.
nodedown = node.one(' > div a.action-movedown');
if (nodedown) {
nodedown.focus();
}
}
this.updated(true);
Y.log('Success: '+this.get('itemname')+' moved up by AJAX.', 'info', 'moodle-course-management');
} else {
@ -898,6 +909,17 @@ Item.prototype = {
nodedown.insert(nextup, 'before');
}
}
nodedown = node.one(' > div a.action-movedown');
if (nodedown) {
// Try to ensure the up is focused again.
nodedown.focus();
} else {
// If we can't focus up we're at the top, try to focus on down.
nodeup = node.one(' > div a.action-moveup');
if (nodeup) {
nodeup.focus();
}
}
this.updated(true);
Y.log('Success: '+this.get('itemname')+' moved down by AJAX.', 'info', 'moodle-course-management');
} else {
@ -918,13 +940,18 @@ Item.prototype = {
* @returns {Boolean}
*/
show : function(transactionid, response, args) {
var outcome = this.checkAjaxResponse(transactionid, response, args);
var outcome = this.checkAjaxResponse(transactionid, response, args),
hidebtn;
if (outcome === false) {
Y.log('AJAX request to show '+this.get('itemname')+' by outcome.', 'warn', 'moodle-course-management');
return false;
}
this.markVisible();
hidebtn = this.get('node').one('a[data-action=hide]');
if (hidebtn) {
hidebtn.focus();
}
this.updated();
Y.log('Success: '+this.get('itemname')+' made visible by AJAX.', 'info', 'moodle-course-management');
},
@ -949,12 +976,17 @@ Item.prototype = {
* @returns {Boolean}
*/
hide : function(transactionid, response, args) {
var outcome = this.checkAjaxResponse(transactionid, response, args);
var outcome = this.checkAjaxResponse(transactionid, response, args),
showbtn;
if (outcome === false) {
Y.log('AJAX request to hide '+this.get('itemname')+' by outcome.', 'warn', 'moodle-course-management');
return false;
}
this.markHidden();
showbtn = this.get('node').one('a[data-action=show]');
if (showbtn) {
showbtn.focus();
}
this.updated();
Y.log('Success: '+this.get('itemname')+' made hidden by AJAX.', 'info', 'moodle-course-management');
},
@ -1148,13 +1180,18 @@ Category.prototype = {
*/
expand : function() {
var node = this.get('node'),
action = node.one('a[data-action=expand]');
action = node.one('a[data-action=expand]'),
ul = node.one('ul[role=group]');
node.removeClass('collapsed').setAttribute('aria-expanded', 'true');
action.setAttribute('data-action', 'collapse').one('img').setAttrs({
action.setAttribute('data-action', 'collapse').setAttrs({
title : M.util.get_string('collapsecategory', 'moodle', this.getName())
}).one('img').setAttrs({
src : M.util.image_url('t/switch_minus', 'moodle'),
title : M.util.get_string('collapse', 'moodle'),
alt : M.util.get_string('collapse', 'moodle')
});
if (ul) {
ul.setAttribute('aria-hidden', 'false');
}
this.get('console').performAjaxAction('expandcategory', {categoryid : this.get('categoryid')}, null, this);
},
@ -1164,13 +1201,18 @@ Category.prototype = {
*/
collapse : function() {
var node = this.get('node'),
action = node.one('a[data-action=collapse]');
action = node.one('a[data-action=collapse]'),
ul = node.one('ul[role=group]');
node.addClass('collapsed').setAttribute('aria-expanded', 'false');
action.setAttribute('data-action', 'expand').one('img').setAttrs({
action.setAttribute('data-action', 'expand').setAttrs({
title : M.util.get_string('expandcategory', 'moodle', this.getName())
}).one('img').setAttrs({
src : M.util.image_url('t/switch_plus', 'moodle'),
title : M.util.get_string('expand', 'moodle'),
alt : M.util.get_string('expand', 'moodle')
});
if (ul) {
ul.setAttribute('aria-hidden', 'true');
}
this.get('console').performAjaxAction('collapsecategory', {categoryid : this.get('categoryid')}, null, this);
},
@ -1187,7 +1229,9 @@ Category.prototype = {
loadSubcategories : function(transactionid, response, args) {
var outcome = this.checkAjaxResponse(transactionid, response, args),
node = this.get('node'),
managementconsole = this.get('console');
managementconsole = this.get('console'),
ul,
actionnode;
if (outcome === false) {
Y.log('AJAX failed to load sub categories for '+this.get('itemname'), 'warn', 'moodle-course-management');
return false;
@ -1198,6 +1242,11 @@ Category.prototype = {
if (M.core && M.core.actionmenu && M.core.actionmenu.newDOMNode) {
M.core.actionmenu.newDOMNode(node);
}
ul = node.one('ul[role=group]');
actionnode = node.one('a[data-action=collapse]');
if (ul && actionnode) {
actionnode.setAttribute('aria-controls', ul.generateID());
}
return true;
},
@ -1295,13 +1344,18 @@ Category.prototype = {
* @returns {Boolean}
*/
show : function(transactionid, response, args) {
var outcome = this.checkAjaxResponse(transactionid, response, args);
var outcome = this.checkAjaxResponse(transactionid, response, args),
hidebtn;
if (outcome === false) {
Y.log('AJAX request to show '+this.get('itemname')+' by outcome.', 'warn', 'moodle-course-management');
return false;
}
this.markVisible();
hidebtn = this.get('node').one('a[data-action=hide]');
if (hidebtn) {
hidebtn.focus();
}
if (outcome.categoryvisibility) {
this.updateChildVisibility(outcome.categoryvisibility);
}
@ -1322,12 +1376,17 @@ Category.prototype = {
* @returns {Boolean}
*/
hide : function(transactionid, response, args) {
var outcome = this.checkAjaxResponse(transactionid, response, args);
var outcome = this.checkAjaxResponse(transactionid, response, args),
showbtn;
if (outcome === false) {
Y.log('AJAX request to hide '+this.get('itemname')+' by outcome.', 'warn', 'moodle-course-management');
return false;
}
this.markHidden();
showbtn = this.get('node').one('a[data-action=show]');
if (showbtn) {
showbtn.focus();
}
if (outcome.categoryvisibility) {
this.updateChildVisibility(outcome.categoryvisibility);
}

File diff suppressed because one or more lines are too long

View File

@ -828,6 +828,17 @@ Item.prototype = {
nodeup.insert(previousdown, 'after');
}
}
nodeup = node.one(' > div a.action-moveup');
if (nodeup) {
// Try to re-focus on up.
nodeup.focus();
} else {
// If we can't focus up we're at the bottom, try to focus on up.
nodedown = node.one(' > div a.action-movedown');
if (nodedown) {
nodedown.focus();
}
}
this.updated(true);
} else {
// Aha it succeeded but this is the top item in the list. Pagination is in play!
@ -882,6 +893,17 @@ Item.prototype = {
nodedown.insert(nextup, 'before');
}
}
nodedown = node.one(' > div a.action-movedown');
if (nodedown) {
// Try to ensure the up is focused again.
nodedown.focus();
} else {
// If we can't focus up we're at the top, try to focus on down.
nodeup = node.one(' > div a.action-moveup');
if (nodeup) {
nodeup.focus();
}
}
this.updated(true);
} else {
// Aha it succeeded but this is the bottom item in the list. Pagination is in play!
@ -900,12 +922,17 @@ Item.prototype = {
* @returns {Boolean}
*/
show : function(transactionid, response, args) {
var outcome = this.checkAjaxResponse(transactionid, response, args);
var outcome = this.checkAjaxResponse(transactionid, response, args),
hidebtn;
if (outcome === false) {
return false;
}
this.markVisible();
hidebtn = this.get('node').one('a[data-action=hide]');
if (hidebtn) {
hidebtn.focus();
}
this.updated();
},
@ -928,11 +955,16 @@ Item.prototype = {
* @returns {Boolean}
*/
hide : function(transactionid, response, args) {
var outcome = this.checkAjaxResponse(transactionid, response, args);
var outcome = this.checkAjaxResponse(transactionid, response, args),
showbtn;
if (outcome === false) {
return false;
}
this.markHidden();
showbtn = this.get('node').one('a[data-action=show]');
if (showbtn) {
showbtn.focus();
}
this.updated();
},
@ -1123,13 +1155,18 @@ Category.prototype = {
*/
expand : function() {
var node = this.get('node'),
action = node.one('a[data-action=expand]');
action = node.one('a[data-action=expand]'),
ul = node.one('ul[role=group]');
node.removeClass('collapsed').setAttribute('aria-expanded', 'true');
action.setAttribute('data-action', 'collapse').one('img').setAttrs({
action.setAttribute('data-action', 'collapse').setAttrs({
title : M.util.get_string('collapsecategory', 'moodle', this.getName())
}).one('img').setAttrs({
src : M.util.image_url('t/switch_minus', 'moodle'),
title : M.util.get_string('collapse', 'moodle'),
alt : M.util.get_string('collapse', 'moodle')
});
if (ul) {
ul.setAttribute('aria-hidden', 'false');
}
this.get('console').performAjaxAction('expandcategory', {categoryid : this.get('categoryid')}, null, this);
},
@ -1139,13 +1176,18 @@ Category.prototype = {
*/
collapse : function() {
var node = this.get('node'),
action = node.one('a[data-action=collapse]');
action = node.one('a[data-action=collapse]'),
ul = node.one('ul[role=group]');
node.addClass('collapsed').setAttribute('aria-expanded', 'false');
action.setAttribute('data-action', 'expand').one('img').setAttrs({
action.setAttribute('data-action', 'expand').setAttrs({
title : M.util.get_string('expandcategory', 'moodle', this.getName())
}).one('img').setAttrs({
src : M.util.image_url('t/switch_plus', 'moodle'),
title : M.util.get_string('expand', 'moodle'),
alt : M.util.get_string('expand', 'moodle')
});
if (ul) {
ul.setAttribute('aria-hidden', 'true');
}
this.get('console').performAjaxAction('collapsecategory', {categoryid : this.get('categoryid')}, null, this);
},
@ -1162,7 +1204,9 @@ Category.prototype = {
loadSubcategories : function(transactionid, response, args) {
var outcome = this.checkAjaxResponse(transactionid, response, args),
node = this.get('node'),
managementconsole = this.get('console');
managementconsole = this.get('console'),
ul,
actionnode;
if (outcome === false) {
return false;
}
@ -1171,6 +1215,11 @@ Category.prototype = {
if (M.core && M.core.actionmenu && M.core.actionmenu.newDOMNode) {
M.core.actionmenu.newDOMNode(node);
}
ul = node.one('ul[role=group]');
actionnode = node.one('a[data-action=collapse]');
if (ul && actionnode) {
actionnode.setAttribute('aria-controls', ul.generateID());
}
return true;
},
@ -1265,12 +1314,17 @@ Category.prototype = {
* @returns {Boolean}
*/
show : function(transactionid, response, args) {
var outcome = this.checkAjaxResponse(transactionid, response, args);
var outcome = this.checkAjaxResponse(transactionid, response, args),
hidebtn;
if (outcome === false) {
return false;
}
this.markVisible();
hidebtn = this.get('node').one('a[data-action=hide]');
if (hidebtn) {
hidebtn.focus();
}
if (outcome.categoryvisibility) {
this.updateChildVisibility(outcome.categoryvisibility);
}
@ -1290,11 +1344,16 @@ Category.prototype = {
* @returns {Boolean}
*/
hide : function(transactionid, response, args) {
var outcome = this.checkAjaxResponse(transactionid, response, args);
var outcome = this.checkAjaxResponse(transactionid, response, args),
showbtn;
if (outcome === false) {
return false;
}
this.markHidden();
showbtn = this.get('node').one('a[data-action=show]');
if (showbtn) {
showbtn.focus();
}
if (outcome.categoryvisibility) {
this.updateChildVisibility(outcome.categoryvisibility);
}

View File

@ -148,13 +148,18 @@ Category.prototype = {
*/
expand : function() {
var node = this.get('node'),
action = node.one('a[data-action=expand]');
action = node.one('a[data-action=expand]'),
ul = node.one('ul[role=group]');
node.removeClass('collapsed').setAttribute('aria-expanded', 'true');
action.setAttribute('data-action', 'collapse').one('img').setAttrs({
action.setAttribute('data-action', 'collapse').setAttrs({
title : M.util.get_string('collapsecategory', 'moodle', this.getName())
}).one('img').setAttrs({
src : M.util.image_url('t/switch_minus', 'moodle'),
title : M.util.get_string('collapse', 'moodle'),
alt : M.util.get_string('collapse', 'moodle')
});
if (ul) {
ul.setAttribute('aria-hidden', 'false');
}
this.get('console').performAjaxAction('expandcategory', {categoryid : this.get('categoryid')}, null, this);
},
@ -164,13 +169,18 @@ Category.prototype = {
*/
collapse : function() {
var node = this.get('node'),
action = node.one('a[data-action=collapse]');
action = node.one('a[data-action=collapse]'),
ul = node.one('ul[role=group]');
node.addClass('collapsed').setAttribute('aria-expanded', 'false');
action.setAttribute('data-action', 'expand').one('img').setAttrs({
action.setAttribute('data-action', 'expand').setAttrs({
title : M.util.get_string('expandcategory', 'moodle', this.getName())
}).one('img').setAttrs({
src : M.util.image_url('t/switch_plus', 'moodle'),
title : M.util.get_string('expand', 'moodle'),
alt : M.util.get_string('expand', 'moodle')
});
if (ul) {
ul.setAttribute('aria-hidden', 'true');
}
this.get('console').performAjaxAction('collapsecategory', {categoryid : this.get('categoryid')}, null, this);
},
@ -187,7 +197,9 @@ Category.prototype = {
loadSubcategories : function(transactionid, response, args) {
var outcome = this.checkAjaxResponse(transactionid, response, args),
node = this.get('node'),
managementconsole = this.get('console');
managementconsole = this.get('console'),
ul,
actionnode;
if (outcome === false) {
Y.log('AJAX failed to load sub categories for '+this.get('itemname'), 'warn', 'moodle-course-management');
return false;
@ -198,6 +210,11 @@ Category.prototype = {
if (M.core && M.core.actionmenu && M.core.actionmenu.newDOMNode) {
M.core.actionmenu.newDOMNode(node);
}
ul = node.one('ul[role=group]');
actionnode = node.one('a[data-action=collapse]');
if (ul && actionnode) {
actionnode.setAttribute('aria-controls', ul.generateID());
}
return true;
},
@ -295,13 +312,18 @@ Category.prototype = {
* @returns {Boolean}
*/
show : function(transactionid, response, args) {
var outcome = this.checkAjaxResponse(transactionid, response, args);
var outcome = this.checkAjaxResponse(transactionid, response, args),
hidebtn;
if (outcome === false) {
Y.log('AJAX request to show '+this.get('itemname')+' by outcome.', 'warn', 'moodle-course-management');
return false;
}
this.markVisible();
hidebtn = this.get('node').one('a[data-action=hide]');
if (hidebtn) {
hidebtn.focus();
}
if (outcome.categoryvisibility) {
this.updateChildVisibility(outcome.categoryvisibility);
}
@ -322,12 +344,17 @@ Category.prototype = {
* @returns {Boolean}
*/
hide : function(transactionid, response, args) {
var outcome = this.checkAjaxResponse(transactionid, response, args);
var outcome = this.checkAjaxResponse(transactionid, response, args),
showbtn;
if (outcome === false) {
Y.log('AJAX request to hide '+this.get('itemname')+' by outcome.', 'warn', 'moodle-course-management');
return false;
}
this.markHidden();
showbtn = this.get('node').one('a[data-action=show]');
if (showbtn) {
showbtn.focus();
}
if (outcome.categoryvisibility) {
this.updateChildVisibility(outcome.categoryvisibility);
}

View File

@ -123,6 +123,17 @@ Item.prototype = {
nodeup.insert(previousdown, 'after');
}
}
nodeup = node.one(' > div a.action-moveup');
if (nodeup) {
// Try to re-focus on up.
nodeup.focus();
} else {
// If we can't focus up we're at the bottom, try to focus on up.
nodedown = node.one(' > div a.action-movedown');
if (nodedown) {
nodedown.focus();
}
}
this.updated(true);
Y.log('Success: '+this.get('itemname')+' moved up by AJAX.', 'info', 'moodle-course-management');
} else {
@ -180,6 +191,17 @@ Item.prototype = {
nodedown.insert(nextup, 'before');
}
}
nodedown = node.one(' > div a.action-movedown');
if (nodedown) {
// Try to ensure the up is focused again.
nodedown.focus();
} else {
// If we can't focus up we're at the top, try to focus on down.
nodeup = node.one(' > div a.action-moveup');
if (nodeup) {
nodeup.focus();
}
}
this.updated(true);
Y.log('Success: '+this.get('itemname')+' moved down by AJAX.', 'info', 'moodle-course-management');
} else {
@ -200,13 +222,18 @@ Item.prototype = {
* @returns {Boolean}
*/
show : function(transactionid, response, args) {
var outcome = this.checkAjaxResponse(transactionid, response, args);
var outcome = this.checkAjaxResponse(transactionid, response, args),
hidebtn;
if (outcome === false) {
Y.log('AJAX request to show '+this.get('itemname')+' by outcome.', 'warn', 'moodle-course-management');
return false;
}
this.markVisible();
hidebtn = this.get('node').one('a[data-action=hide]');
if (hidebtn) {
hidebtn.focus();
}
this.updated();
Y.log('Success: '+this.get('itemname')+' made visible by AJAX.', 'info', 'moodle-course-management');
},
@ -231,12 +258,17 @@ Item.prototype = {
* @returns {Boolean}
*/
hide : function(transactionid, response, args) {
var outcome = this.checkAjaxResponse(transactionid, response, args);
var outcome = this.checkAjaxResponse(transactionid, response, args),
showbtn;
if (outcome === false) {
Y.log('AJAX request to hide '+this.get('itemname')+' by outcome.', 'warn', 'moodle-course-management');
return false;
}
this.markHidden();
showbtn = this.get('node').one('a[data-action=show]');
if (showbtn) {
showbtn.focus();
}
this.updated();
Y.log('Success: '+this.get('itemname')+' made hidden by AJAX.', 'info', 'moodle-course-management');
},

View File

@ -227,6 +227,7 @@ $string['categorydeleted'] = 'The category \'{$a}\' was deleted';
$string['categoryduplicate'] = 'A category named \'{$a}\' already exists!';
$string['categorymodifiedcancel'] = 'Category was modified! Please cancel and try again.';
$string['categoryname'] = 'Category name';
$string['categorysubcategoryof'] = '{$a->category} - subcategory of {$a->parentcategory}';
$string['idnumbercoursecategory'] = 'Category ID number';
$string['idnumbercoursecategory_help'] = 'The ID number of a course category is only used when matching the category against external systems and is not displayed anywhere on the site. If the category has an official code name it may be entered, otherwise the field can be left blank.';
$string['categoryupdated'] = 'The category \'{$a}\' was updated';
@ -252,6 +253,7 @@ $string['clicktochangeinbrackets'] = '{$a} (Click to change)';
$string['closewindow'] = 'Close this window';
$string['collapse'] = 'Collapse';
$string['collapseall'] = 'Collapse all';
$string['collapsecategory'] = 'Collapse {$a}';
$string['commentincontext'] = 'Find this comment in context';
$string['comments'] = 'Comments';
$string['commentsnotenabled'] = 'Comments feature is not enabled';
@ -735,6 +737,7 @@ $string['existingstudents'] = 'Enrolled students';
$string['existingteachers'] = 'Existing teachers';
$string['expand'] = 'Expand';
$string['expandall'] = 'Expand all';
$string['expandcategory'] = 'Expand {$a}';
$string['explanation'] = 'Explanation';
$string['extendenrol'] = 'Extend enrolment (individual)';
$string['extendperiod'] = 'Extended period';
@ -1602,6 +1605,9 @@ $string['selectmoduletoviewhelp'] = 'Select an activity or resource to view its
Double-click on an activity or resource name to quickly add it.';
$string['selectnos'] = 'Select all \'No\'';
$string['selectperiod'] = 'Select period';
$string['selectcategorysort'] = 'Which categories would you like to sort';
$string['selectcategorysortby'] = 'Select how you would like to sort categories';
$string['selectcoursesortby'] = 'Select how you would like to sort courses';
$string['senddetails'] = 'Send my details via email';
$string['separate'] = 'Separate';
$string['separateandconnected'] = 'Separate and Connected ways of knowing';
@ -1626,6 +1632,7 @@ $string['showall'] = 'Show all {$a}';
$string['showallcourses'] = 'Show all courses';
$string['showallusers'] = 'Show all users';
$string['showblockcourse'] = 'Show list of courses containing block';
$string['showcategory'] = 'Show {$a}';
$string['showcomments'] = 'Show/hide comments';
$string['showcommentsnonjs'] = 'Show comments';
$string['showdescription'] = 'Display description on course page';
@ -1670,6 +1677,9 @@ $string['sizegb'] = 'GB';
$string['sizekb'] = 'KB';
$string['sizemb'] = 'MB';
$string['skipped'] = 'Skipped';
$string['skiptocategorylisting'] = 'Skip to the category listings';
$string['skiptocourselisting'] = 'Skip to the course listings';
$string['skiptocoursedetails'] = 'Skip to the detailed course information';
$string['skypeid'] = 'Skype ID';
$string['socialheadline'] = 'Social forum - latest topics';
$string['someallowguest'] = 'Some courses may allow guest access';

View File

@ -1114,11 +1114,13 @@ class core_renderer extends renderer_base {
* @return string HTML fragment
*/
protected function render_action_menu_link(action_menu_link $action) {
static $actioncount = 0;
$actioncount++;
$comparetoalt = '';
$text = '';
if (!$action->icon || $action->primary === false) {
$text .= html_writer::start_tag('span', array('class'=>'menu-action-text'));
$text .= html_writer::start_tag('span', array('class'=>'menu-action-text', 'id' => 'actionmenuaction-'.$actioncount));
if ($action->text instanceof renderable) {
$text .= $this->render($action->text);
} else {
@ -1134,12 +1136,9 @@ class core_renderer extends renderer_base {
if ($action->primary || !$action->actionmenu->will_be_enhanced()) {
$action->attributes['title'] = $action->text;
}
if ((string)$icon->attributes['alt'] === $comparetoalt && $action->actionmenu->will_be_enhanced()) {
$icon->attributes['alt'] = ' ';
}
if (!$action->primary && $action->actionmenu->will_be_enhanced()) {
if ((string)$icon->attributes['alt'] === $comparetoalt) {
$icon->attributes['alt'] = ' ';
$icon->attributes['alt'] = '';
}
if (isset($icon->attributes['title']) && (string)$icon->attributes['title'] === $comparetoalt) {
unset($icon->attributes['title']);
@ -1157,6 +1156,9 @@ class core_renderer extends renderer_base {
$attributes = $action->attributes;
unset($action->attributes['disabled']);
$attributes['href'] = $action->url;
if ($text !== '') {
$attributes['aria-labelledby'] = 'actionmenuaction-'.$actioncount;
}
return html_writer::tag('a', $icon.$text, $attributes);
}

View File

@ -501,7 +501,8 @@ input.titleeditor { vertical-align: text-bottom; }
/** Management header styling **/
.coursecat-management-header {vertical-align:middle;}
.coursecat-management-header h2 {display:inline-block;text-align:left;}
.coursecat-management-header > div {display:inline-block;float:right;margin-left:1em;}
.coursecat-management-header > div {display:inline-block;float:right;}
.coursecat-management-header > div > div {display:inline-block;margin-left:1em;}
.dir-rtl .coursecat-management-header h2 {text-align:right;}
.dir-rtl .coursecat-management-header > div {float:left;margin-right:1em;margin-left:0;}
.coursecat-management-header .view-mode-selector img {margin-left:0.5em;vertical-align: baseline;}

View File

@ -1240,9 +1240,12 @@ span.editinstructions {
> div {
display:inline-block;
float:right;
margin-left:1em;
margin: 10px 0;
line-height:40px;
> div {
margin-left:1em;
margin: 10px 0;
display:inline-block;
}
}
select {
max-width: 300px;

File diff suppressed because one or more lines are too long