MDL-36296 block_navigation: consistency fixes

This change fixes a couple of consistency issues in the navigation block.
At the same time it fixes it the CSS for the navigation block as it had
really degraded over the last years.
Notable points about this change:

* Fixed icon display by wrapping content in a span allowing columns to
  be styled.
* Re-wrote the CSS entirely as it could be done much better.
* Fixed inconsistencies between the php and JS for rendering nodes.
This commit is contained in:
Sam Hemelryk 2014-11-28 09:15:44 +13:00 committed by Sam Hemelryk
parent 4c27f52d91
commit 892e21aadd
6 changed files with 167 additions and 87 deletions

View file

@ -51,7 +51,7 @@ class block_navigation_renderer extends plugin_renderer_base {
/**
* Produces a navigation node for the navigation tree
*
* @param array $items
* @param navigation_node[] $items
* @param array $attrs
* @param int $expansionlimit
* @param array $options
@ -59,13 +59,12 @@ class block_navigation_renderer extends plugin_renderer_base {
* @return string
*/
protected function navigation_node($items, $attrs=array(), $expansionlimit=null, array $options = array(), $depth=1) {
// exit if empty, we don't want an empty ul element
if (count($items)==0) {
// Exit if empty, we don't want an empty ul element.
if (count($items) === 0) {
return '';
}
// array of nested li elements
// Turn our navigation items into list items.
$lis = array();
foreach ($items as $item) {
if (!$item->display && !$item->contains_active_node()) {
@ -86,10 +85,13 @@ class block_navigation_renderer extends plugin_renderer_base {
if ($hasicon) {
$icon = $this->output->render($item->icon);
// Because an icon is being used we're going to wrap the actual content in a span.
// This will allow designers to create columns for the content, as we've done in styles.css.
$content = $icon . html_writer::span($content, 'item-content-wrap');
} else {
$icon = '';
}
$content = $icon.$content; // use CSS for spacing of icons
if ($item->helpbutton !== null) {
$content = trim($item->helpbutton).html_writer::tag('span', $content, array('class'=>'clearhelpbutton'));
}
@ -116,13 +118,16 @@ class block_navigation_renderer extends plugin_renderer_base {
$link->text = $icon.$link->text;
$link->attributes = array_merge($link->attributes, $attributes);
$content = $this->output->render($link);
$linkrendered = true;
} else if ($item->action instanceof moodle_url) {
$content = html_writer::link($item->action, $content, $attributes);
}
// this applies to the li item which contains all child lists too
// This applies to the li item which contains all child lists too.
$liclasses = array($item->get_css_type(), 'depth_'.$depth);
// Class attribute on the div item which only contains the item content.
$divclasses = array('tree_item');
$liexpandable = array();
if ($item->has_children() && (!$item->forceopen || $item->collapse)) {
$liclasses[] = 'collapsed';
@ -130,30 +135,30 @@ class block_navigation_renderer extends plugin_renderer_base {
if ($isbranch) {
$liclasses[] = 'contains_branch';
$liexpandable = array('aria-expanded' => in_array('collapsed', $liclasses) ? "false" : "true");
} else if ($hasicon) {
$liclasses[] = 'item_with_icon';
}
if ($item->isactive === true) {
$liclasses[] = 'current_branch';
}
$liattr = array('class' => join(' ',$liclasses)) + $liexpandable;
// class attribute on the div item which only contains the item content
$divclasses = array('tree_item');
if ($isbranch) {
$divclasses[] = 'branch';
} else {
$divclasses[] = 'leaf';
}
if ($hasicon) {
// Add this class if the item has an icon, whether it is a branch or not.
$liclasses[] = 'item_with_icon';
$divclasses[] = 'hasicon';
}
if ($item->isactive === true) {
$liclasses[] = 'current_branch';
}
if (!empty($item->classes) && count($item->classes)>0) {
$divclasses[] = join(' ', $item->classes);
}
// Now build attribute arrays.
$liattr = array('class' => join(' ', $liclasses)) + $liexpandable;
$divattr = array('class'=>join(' ', $divclasses));
if (!empty($item->id)) {
$divattr['id'] = $item->id;
}
// Create the structure.
$content = html_writer::tag('p', $content, $divattr);
if ($isexpandable) {
$content .= $this->navigation_node($item->children, array(), $expansionlimit, $options, $depth+1);
@ -165,11 +170,14 @@ class block_navigation_renderer extends plugin_renderer_base {
$lis[] = $content;
}
if (count($lis)) {
return html_writer::tag('ul', implode("\n", $lis), $attrs);
} else {
if (count($lis) === 0) {
// There is still a chance, despite having items, that nothing had content and no list items were created.
return '';
}
// We used to separate using new lines, however we don't do that now, instead we'll save a few chars.
// The source is complex already anyway.
return html_writer::tag('ul', implode('', $lis), $attrs);
}
}

View file

@ -1,66 +1,105 @@
/** General display rules **/
.block_navigation .block_tree {margin:5px;padding-left:0px;overflow:visible;}
.block_navigation .block_tree li {margin:3px;list-style: none;padding:0;}
.block_navigation .block_tree li.item_with_icon > p {position:relative; padding-left: 21px;}
.block_navigation .block_tree li.item_with_icon > p img,
.block_navigation .block_tree .type_activity > p.tree_item.active_tree_node img,
.block_navigation .block_tree li > p.hasicon img {vertical-align:middle;position:absolute;left:0;top:-1px;width:16px;height:16px;}
.block_navigation .block_tree li.item_with_icon.contains_branch > p img {left:16px;}
.block_navigation .block_tree .type_activity > p.branch.hasicon,
.block_navigation .block_tree .type_activity > p.emptybranch.hasicon,
.block_navigation .block_tree li.item_with_icon.contains_branch > .tree_item {padding-left:37px;}
.block_navigation .block_tree {
margin: 0;
list-style: none;
}
.block_navigation .block_tree li ul {padding-left:0;margin:0;}
.block_navigation .block_tree li.depth_2 ul {padding-left:16px;margin:0;}
.block_navigation .block_tree .type_activity > p.tree_item.branch.hasicon.active_tree_node,
.block_navigation .block_tree .tree_item {padding-left: 21px;margin:3px 0px;text-align:left;}
.block_navigation .block_tree .depth_1 > .tree_item.branch,
.block_navigation .block_tree .depth_1 > .tree_item.emptybranch {
padding-left: 0;
background-image: none;
}
.block_navigation .block_tree .tree_item.branch {background-image: url([[pix:t/expanded]]);background-position: 0 0;background-repeat: no-repeat;}
.block_navigation .block_tree .tree_item.branch.navigation_node {background-image:none;padding-left:0;}
.block_navigation .block_tree .type_activity > .tree_item.emptybranch,
.block_navigation .block_tree .type_activity > .tree_item.branch {background-image:none;position:relative;}
.block_navigation .block_tree .type_activity > .tree_item.hasicon.emptybranch img,
.block_navigation .block_tree .type_activity > .tree_item.branch img {left: 16px;}
.block_navigation .block_tree .root_node.leaf {padding-left:0px;}
.block_navigation .block_tree .active_tree_node {font-weight:bold;}
.block_navigation .block_tree .depth_1.current_branch ul {font-weight:normal;}
.block_navigation .block_tree .tree_item {
margin: 3px 0;
background-position: 0 50%;
background-repeat: no-repeat;
}
.dock .block_navigation .tree_item {white-space: nowrap;}
.block_navigation .block_tree .tree_item.branch {
padding-left: 21px;
cursor: pointer;
background-image: url('[[pix:t/expanded]]');
}
.jsenabled .block_navigation .block_tree .tree_item.branch {cursor:pointer;}
.jsenabled .block_navigation .block_tree .tree_item.emptybranch {background-image: url([[pix:t/collapsed_empty]]);background-position: 0 0;background-repeat: no-repeat;}
.jsenabled .block_navigation .block_tree .collapsed ul {display: none;}
.jsenabled .block_navigation .block_tree .type_activity > .tree_item.branch {background-image: url([[pix:t/expanded]]);}
.jsenabled .block_navigation .block_tree .collapsed .tree_item.branch {background-image: url([[pix:t/collapsed]]);}
.jsenabled .block_navigation .block_tree .tree_item.branch.loadingbranch {background-image:url([[pix:i/loading_small]]);}
.block_navigation .block_tree .tree_item.emptybranch {
padding-left: 21px;
background-image: url('[[pix:t/collapsed_empty]]');
}
/** JavaScript state rules **/
.jsenabled .block_navigation.dock_on_load,
.block_navigation .block_tree_box .requiresjs {display:none;}
.jsenabled .block_navigation .block_tree_box .requiresjs {display:inline;}
.block_navigation .block_tree .tree_item.loadingbranch {
background-image: url('[[pix:i/loading_small]]');
}
/** Internet explorer specific rules **/
.ie6 .block_navigation .block_tree .tree_item {width:100%;}
.block_navigation .block_tree .tree_item img {
width: 16px;
height: 16px;
margin-top: 3px;
margin-right: 5px;
vertical-align: top;
}
/** Overide for RTL layout **/
.dir-rtl .block_navigation .block_tree li.depth_2 ul {padding-left: 0; padding-right: 16px;}
.dir-rtl .block_navigation .block_tree .type_activity > p.tree_item.branch.hasicon.active_tree_node,
.dir-rtl .block_navigation .block_tree .tree_item {padding-right: 21px;text-align:right;}
.block_navigation .block_tree .tree_item.active_tree_node {
font-weight: bold;
}
.dir-rtl .block_navigation .block_tree .tree_item.branch {background-position: center right;}
.block_navigation .block_tree .tree_item.hasicon {
white-space: nowrap;
}
.dir-rtl .block_navigation .block_tree,
.dir-rtl .block_navigation .block_tree li ul,
.dir-rtl .block_navigation .block_tree .navigation_node.tree_item.branch,
.dir-rtl .block_navigation .block_tree .root_node.leaf {padding-right:0;}
.block_navigation .block_tree .tree_item.hasicon .item-content-wrap {
display: inline-block;
white-space: normal;
}
.dir-rtl .block_navigation .block_tree li.item_with_icon > p img,
.dir-rtl .block_navigation .block_tree .type_activity > p.tree_item.active_tree_node img,
.dir-rtl .block_navigation .block_tree li > p.hasicon img {left:auto; right:0;}
.dir-rtl .block_navigation .block_tree li.item_with_icon.contains_branch > p img {left: auto; right:16px;}
.dir-rtl .block_navigation .block_tree .type_activity > p.branch.hasicon,
.dir-rtl .block_navigation .block_tree li.item_with_icon.contains_branch > .tree_item {padding-right:37px; padding-left: 0;}
.dir-rtl .block_navigation .block_tree .type_activity > .tree_item.branch img {right: 16px; left: auto;}
.block_navigation .block_tree ul {
margin: 0;
}
.jsenabled.dir-rtl .block_navigation .block_tree .tree_item.emptybranch {background-image: url([[pix:t/collapsed_empty_rtl]]);background-position: center right;}
.jsenabled.dir-rtl .block_navigation .block_tree .collapsed .tree_item.branch {background-image: url([[pix:t/collapsed_rtl]]);}
.block_navigation .block_tree ul ul {
margin: 0 0 0 16px;
list-style: none;
}
.jsenabled .block_navigation .block_tree li.collapsed ul {
display: none;
}
.jsenabled .block_navigation .block_tree li.collapsed .tree_item.branch {
background-image: url('[[pix:t/collapsed]]');
}
.jsenabled .block_navigation.dock_on_load {
display: none;
}
.dir-rtl .block_navigation .block_tree .depth_1 .tree_item {
padding-left: 0;
}
.dir-rtl .block_navigation .block_tree .tree_item {
background-position: 100% 50%;
}
.dir-rtl .block_navigation .block_tree .tree_item.branch {
padding-right: 21px;
padding-left: 0;
}
.dir-rtl .block_navigation .block_tree .tree_item.emptybranch {
padding-right: 21px;
padding-left: 0;
background-image: url('[[pix:t/collapsed_empty_rtl]]');
}
.dir-rtl .block_navigation .block_tree .tree_item img {
margin-right: 0;
margin-left: 5px;
}
.dir-rtl .block_navigation .block_tree ul {
margin: 0 16px 0 0;
}
.dir-rtl.jsenabled .block_navigation .block_tree .collapsed .tree_item.branch {
background-image: url('[[pix:t/collapsed_rtl]]');
}

View file

@ -483,6 +483,9 @@ BRANCH.prototype = {
* This function creates a DOM structure for the branch and then injects
* it into the navigation tree at the correct point.
*
* It is important that this is kept in check with block_navigation_renderer::navigation_node as that produces
* the same thing as this but on the php side.
*
* @method draw
* @chainable
* @param {Node} element
@ -494,6 +497,7 @@ BRANCH.prototype = {
var branchli = Y.Node.create('<li></li>');
var link = this.get('link');
var branchp = Y.Node.create('<p class="tree_item"></p>').setAttribute('id', this.get('id'));
var name;
if (!link) {
//add tab focus if not link (so still one focus per menu node).
// it was suggested to have 2 foci. one for the node and one for the link in MDL-27428.
@ -508,10 +512,11 @@ BRANCH.prototype = {
// Prepare the icon, should be an object representing a pix_icon
var branchicon = false;
var icon = this.get('icon');
if (icon && (!isbranch || this.get('type') === NODETYPE.ACTIVITY)) {
if (icon && (!isbranch || this.get('type') === NODETYPE.ACTIVITY || this.get('type') === NODETYPE.RESOURCE)) {
branchicon = Y.Node.create('<img alt="" />');
branchicon.setAttribute('src', M.util.image_url(icon.pix, icon.component));
branchli.addClass('item_with_icon');
branchp.addClass('hasicon');
if (icon.alt) {
branchicon.setAttribute('alt', icon.alt);
}
@ -529,8 +534,11 @@ BRANCH.prototype = {
var branchspan = Y.Node.create('<span></span>');
if (branchicon) {
branchspan.appendChild(branchicon);
name = '<span class="item-content-wrap">' + this.get('name') + '</span>';
} else {
name = this.get('name');
}
branchspan.append(this.get('name'));
branchspan.append(name);
if (this.get('hidden')) {
branchspan.addClass('dimmed_text');
}
@ -539,8 +547,11 @@ BRANCH.prototype = {
var branchlink = Y.Node.create('<a title="'+this.get('title')+'" href="'+link+'"></a>');
if (branchicon) {
branchlink.appendChild(branchicon);
name = '<span class="item-content-wrap">' + this.get('name') + '</span>';
} else {
name = this.get('name');
}
branchlink.append(this.get('name'));
branchlink.append(name);
if (this.get('hidden')) {
branchlink.addClass('dimmed');
}

View file

@ -482,6 +482,9 @@ BRANCH.prototype = {
* This function creates a DOM structure for the branch and then injects
* it into the navigation tree at the correct point.
*
* It is important that this is kept in check with block_navigation_renderer::navigation_node as that produces
* the same thing as this but on the php side.
*
* @method draw
* @chainable
* @param {Node} element
@ -493,6 +496,7 @@ BRANCH.prototype = {
var branchli = Y.Node.create('<li></li>');
var link = this.get('link');
var branchp = Y.Node.create('<p class="tree_item"></p>').setAttribute('id', this.get('id'));
var name;
if (!link) {
//add tab focus if not link (so still one focus per menu node).
// it was suggested to have 2 foci. one for the node and one for the link in MDL-27428.
@ -507,10 +511,11 @@ BRANCH.prototype = {
// Prepare the icon, should be an object representing a pix_icon
var branchicon = false;
var icon = this.get('icon');
if (icon && (!isbranch || this.get('type') === NODETYPE.ACTIVITY)) {
if (icon && (!isbranch || this.get('type') === NODETYPE.ACTIVITY || this.get('type') === NODETYPE.RESOURCE)) {
branchicon = Y.Node.create('<img alt="" />');
branchicon.setAttribute('src', M.util.image_url(icon.pix, icon.component));
branchli.addClass('item_with_icon');
branchp.addClass('hasicon');
if (icon.alt) {
branchicon.setAttribute('alt', icon.alt);
}
@ -528,8 +533,11 @@ BRANCH.prototype = {
var branchspan = Y.Node.create('<span></span>');
if (branchicon) {
branchspan.appendChild(branchicon);
name = '<span class="item-content-wrap">' + this.get('name') + '</span>';
} else {
name = this.get('name');
}
branchspan.append(this.get('name'));
branchspan.append(name);
if (this.get('hidden')) {
branchspan.addClass('dimmed_text');
}
@ -538,8 +546,11 @@ BRANCH.prototype = {
var branchlink = Y.Node.create('<a title="'+this.get('title')+'" href="'+link+'"></a>');
if (branchicon) {
branchlink.appendChild(branchicon);
name = '<span class="item-content-wrap">' + this.get('name') + '</span>';
} else {
name = this.get('name');
}
branchlink.append(this.get('name'));
branchlink.append(name);
if (this.get('hidden')) {
branchlink.addClass('dimmed');
}

View file

@ -481,6 +481,9 @@ BRANCH.prototype = {
* This function creates a DOM structure for the branch and then injects
* it into the navigation tree at the correct point.
*
* It is important that this is kept in check with block_navigation_renderer::navigation_node as that produces
* the same thing as this but on the php side.
*
* @method draw
* @chainable
* @param {Node} element
@ -492,6 +495,7 @@ BRANCH.prototype = {
var branchli = Y.Node.create('<li></li>');
var link = this.get('link');
var branchp = Y.Node.create('<p class="tree_item"></p>').setAttribute('id', this.get('id'));
var name;
if (!link) {
//add tab focus if not link (so still one focus per menu node).
// it was suggested to have 2 foci. one for the node and one for the link in MDL-27428.
@ -506,10 +510,11 @@ BRANCH.prototype = {
// Prepare the icon, should be an object representing a pix_icon
var branchicon = false;
var icon = this.get('icon');
if (icon && (!isbranch || this.get('type') === NODETYPE.ACTIVITY)) {
if (icon && (!isbranch || this.get('type') === NODETYPE.ACTIVITY || this.get('type') === NODETYPE.RESOURCE)) {
branchicon = Y.Node.create('<img alt="" />');
branchicon.setAttribute('src', M.util.image_url(icon.pix, icon.component));
branchli.addClass('item_with_icon');
branchp.addClass('hasicon');
if (icon.alt) {
branchicon.setAttribute('alt', icon.alt);
}
@ -527,8 +532,11 @@ BRANCH.prototype = {
var branchspan = Y.Node.create('<span></span>');
if (branchicon) {
branchspan.appendChild(branchicon);
name = '<span class="item-content-wrap">' + this.get('name') + '</span>';
} else {
name = this.get('name');
}
branchspan.append(this.get('name'));
branchspan.append(name);
if (this.get('hidden')) {
branchspan.addClass('dimmed_text');
}
@ -537,8 +545,11 @@ BRANCH.prototype = {
var branchlink = Y.Node.create('<a title="'+this.get('title')+'" href="'+link+'"></a>');
if (branchicon) {
branchlink.appendChild(branchicon);
name = '<span class="item-content-wrap">' + this.get('name') + '</span>';
} else {
name = this.get('name');
}
branchlink.append(this.get('name'));
branchlink.append(name);
if (this.get('hidden')) {
branchlink.addClass('dimmed');
}