From d7d6f5179cf2eaeea81e85ecc261897fe51ea1bf Mon Sep 17 00:00:00 2001 From: Mark Triggs Date: Sat, 30 Apr 2016 01:42:12 +1000 Subject: [PATCH] SAK-30530 Sakai Favorite Site Tool Dropdown Menu Enhancements (#2395) * Show the tool dropdown for the active site as well. * Left align menus and adjust styles to match * Improve keyboard handling for tool menus * Close the active tool menu when clicking outside of the menu --- .../js/src/sakai.morpheus.subnav.js | 301 ++++++++++-------- .../sass/modules/navigation/_base.scss | 74 ++++- 2 files changed, 231 insertions(+), 144 deletions(-) diff --git a/reference/library/src/morpheus-master/js/src/sakai.morpheus.subnav.js b/reference/library/src/morpheus-master/js/src/sakai.morpheus.subnav.js index b42091366726..722bf4666a2c 100644 --- a/reference/library/src/morpheus-master/js/src/sakai.morpheus.subnav.js +++ b/reference/library/src/morpheus-master/js/src/sakai.morpheus.subnav.js @@ -2,181 +2,206 @@ * Sets up subnav on the sitenav */ -var setupSiteNav = function(){ +var closeAllDropdownMenus = function() { + $PBJQ('.Mrphs-sitesNav__menuitem').removeClass('dropdown-is-visible'); + $PBJQ('.Mrphs-sitesNav__menuitem').find('.is-visible').removeClass('is-visible'); + $PBJQ('.Mrphs-sitesNav__menuitem').find('.is-clicked').removeClass('is-clicked'); + + $PBJQ('.sitenav-dropdown-overlay').remove(); +}; + +var buildDropdownMenu = function(container, siteId, callback) { + var navsubmenu = "" - fixTopNav = function(e) { + navsubmenu = $(navsubmenu); - if (e.keyCode == 40) { // downarrow - - e.preventDefault(); - $PBJQ('#selectSite').hide(); - //$PBJQ('.nav-submenu').hide(); - // Trigger click on the drop , passing true to set focus on - // the first tool in the dropdown. - $PBJQ(this).parent().find(".Mrphs-sitesNav__dropdown").trigger('click',[true]); + container.append(navsubmenu); - } else if (e.keyCode == 27) { // uparrow ? or ESC + addArrowNavAndDisableTabNav(navsubmenu); - $PBJQ(this).parent().children('a').focus(); - $PBJQ(this).toggleClass('is-visible'); + callback(navsubmenu); + }, + error: function(XMLHttpRequest, status, error){ + // Something happened getting the tool list. } - } - - // SAK-25505 - Switch from live() to on() - // $PBJQ( "a.offsite" ).live( "click", function() { - // $PBJQ('.topnav > li.nav-menu > a').live('keydown', function(e){ - if($PBJQ(document).on) { + }); +}; - $PBJQ(document).on('keydown', '.Mrphs-sitesNav__menu > li.Mrphs-sitesNav__menuitem > a', fixTopNav); - } else { +var setupSiteNav = function(){ - $PBJQ('.Mrphs-sitesNav__menu > .Mrphs-sitesNav__menuitem > a').live('keydown', fixTopNav); + $PBJQ("ul.Mrphs-sitesNav__menu").each(function(){ - } - - $PBJQ("ul.Mrphs-sitesNav__menu > li").mouseleave(function(){ - $PBJQ(this).find('ul').toggleClass('is-visible'); + // Add an escape key handler to slide the page menu up + $PBJQ(this).keydown(function(e) { + if (e.keyCode == 27) { + closeAllDropdownMenus(); + } + }); }); + + $PBJQ(document).on('keydown', '.Mrphs-sitesNav__menu > li.Mrphs-sitesNav__menuitem > a', + function (e) { + if (e.keyCode == 40) { + // downarrow + e.preventDefault(); + // Trigger click on the drop , passing true to set focus on + // the first tool in the dropdown. + var dropdown = $PBJQ(this).parent().find(".Mrphs-sitesNav__dropdown"); + + if (dropdown.data('clicked')) { + // If the user has already triggered a click, give the + // AJAX a chance to finish. + } else { + dropdown.data('clicked', true); + dropdown.trigger('click', [true]); + } + } else if (e.keyCode == 27) { + // escape + e.preventDefault(); + closeAllDropdownMenus(); + } + + }); + // focusFirstLink is only ever passed from the keydown handler. We // don't want to focus on click; it looks odd. - $PBJQ("ul.Mrphs-sitesNav__menu li span.Mrphs-sitesNav__dropdown").click(function(e, focusFirstLink) { + e.preventDefault() - /* - * see if there is a menu sibling - * if there is a child, display it - * if no menu sibling - * retrieve data, construct the menu, append - */ + var jqObjDrop = $PBJQ(e.target); + var container = jqObjDrop.closest('.Mrphs-sitesNav__menuitem'); - $PBJQ(this).toggleClass("is-clicked"); //On click toggle class "is-clicked" - e.preventDefault() + var dropdownWasShown = container.hasClass('dropdown-is-visible'); - var jqObjDrop = $PBJQ(e.target); + // Hide any currently shown menus so we don't end up with multiple dropdowns shown + closeAllDropdownMenus(); - if(jqObjDrop.next('ul').length) { - jqObjDrop.next('ul').toggleClass('is-visible'); - - if(focusFirstLink) { - jqObjDrop.next('ul').find("a:first").focus(); + if (dropdownWasShown) { + // We've hidden the dropdown now, so all done. + return; } - } + var dropdownArrow = $PBJQ(this); - else { + var displayDropdown = function (navsubmenu) { + // Mark the dropdown arrow and the menu itself as clicked + dropdownArrow.addClass("is-clicked"); + container.addClass('dropdown-is-visible'); - var navsubmenu = "
    "; - var siteId = jqObjDrop.attr('data-site-id'); - var maxToolsInt = parseInt($PBJQ('#linkNav').attr('data-max-tools-int')); - var maxToolsText = $PBJQ('#linkNav').attr('data-max-tools-anchor'); - var goToSite = '
  • ' + maxToolsText + '
  • '; - var siteURL = '/direct/site/' + jqObjDrop.attr('data-site-id') + '/pages.json'; - var currentSite = window.location.pathname.split('/').pop(); + // now display the menu + navsubmenu.addClass('is-visible'); - $PBJQ.ajax({ - url: siteURL, - dataType: "json", - success: function(data){ + if(focusFirstLink) { + container.find('a.Mrphs-sitesNav__submenuitem-title').first().focus(); + } - $PBJQ.each(data, function(i, item) { + // Add an invisible overlay to allow clicks to close the dropdown + var overlay = $('
" - - jqObjDrop.after(navsubmenu); - - if(focusFirstLink) { - jqObjDrop.next('ul').find("a:first").focus(); - } + if (!container.find('ul').length) { + // We haven't yet built the dropdown menu for this item. Do that now. + buildDropdownMenu(container, jqObjDrop.attr('data-site-id'), displayDropdown); + } else { + displayDropdown(container.find('ul')); + } - addArrowNavAndDisableTabNav($PBJQ(".Mrphs-sitesNav__submenu")); + }).hover(function(){ + $PBJQ(this).toggleClass("Mrphs-sitesNav__dropdown--hover"); //On hover over, add + }); +} - }, +/* Callback is a function and is called after sliding up ul */ +function addArrowNavAndDisableTabNav(ul) { + ul.find('li a').attr('tabindex','-1').keydown(function (e) { + var obj = $PBJQ(e.target); + if (e.keyCode == 40) { + // Down arrow. Move to the next item, or loop around if we're at the bottom. + e.preventDefault(); + var next = obj.closest('li').next(); - error: function(XMLHttpRequest, status, error){ - // Something happened getting the tool list. + if (next.length === 0 || next.find('a.Mrphs-sitesNav__submenuitem-title').length == 0) { + // loop around + next = ul.find('li').first(); } - }); - } - }).hover(function(){ - $PBJQ(this).toggleClass("Mrphs-sitesNav__dropdown--hover"); //On hover over, add - }); + next.find('a.Mrphs-sitesNav__submenuitem-title').focus(); -} + } else if (e.keyCode == 38) { + // Up arrow. Move to the previous item, or loop around if we're at the top. + e.preventDefault(); + var prev = obj.closest('li').prev(); -/* Callback is a function and is called after sliding up ul */ -function addArrowNavAndDisableTabNav(ul,callback) { - ul.find('li a').attr('tabindex','-1').keydown(function (e) { - var obj = $PBJQ(e.target); - if(e.keyCode == 40) { - e.preventDefault(); - var next = obj.parent().parent().next(); - if(next[0] === undefined) { - ul.slideUp('fast'); - if(callback !== undefined) { - callback(); - } else { - obj.parent().parent().parent().parent().children('a').focus(); - } - } else { - next.find('a').focus(); - } - } else if(e.keyCode == 9) { // Suck up the menu if tab is pressed - ul.slideUp('fast'); - } else if(e.keyCode == 38) { - // Up arrow - e.preventDefault(); - var prev = obj.parent().parent().prev(); - if(prev[0] === undefined) { - ul.slideUp('fast'); - if(callback !== undefined) { - callback(); - } else { - obj.parent().parent().parent().parent().children('a').focus(); - } - } else { - prev.find('a').focus(); - } - } - }); + if (prev.length === 0) { + // jump to the bottom + prev = ul.find('a.Mrphs-sitesNav__submenuitem-title').closest('ul') + } + + prev.find('a.Mrphs-sitesNav__submenuitem-title').focus(); + + } else if (e.keyCode == 9) { // Suck up the menu if tab is pressed + closeAllDropdownMenus(); + } + }); } diff --git a/reference/library/src/morpheus-master/sass/modules/navigation/_base.scss b/reference/library/src/morpheus-master/sass/modules/navigation/_base.scss index 2a948f048a10..0283a69b005c 100644 --- a/reference/library/src/morpheus-master/sass/modules/navigation/_base.scss +++ b/reference/library/src/morpheus-master/sass/modules/navigation/_base.scss @@ -522,6 +522,7 @@ body.is-logged-out{ ul{ font-family: $header-font-family; margin: 0 0 0 0; + padding: 5px; li.#{$namespace}sitesNav__menuitem{ display: inline-block; margin: 0.5em 0 0 0; @@ -534,6 +535,7 @@ body.is-logged-out{ position: relative; text-decoration: none; top: -3px; + &:hover{ color: $primary-color; } @@ -552,21 +554,60 @@ body.is-logged-out{ } } ul{ - background: darken( $background-color-secondary, 5% ); + background: $background-color; display: none; - @extend .userNav__subnav; + font-family: $header-font-family; + position: absolute; + border-top: 1px solid black; + margin-top: -4px; + width: 26rem; + z-index: 99; + box-shadow: 0 3px 2px rgba( $text-color, 0.35); + li{ + display: block; + border: 0px none; + @include border-radius( 0 ); + margin: 0 0 0 0; + padding: 0 0 0 0; + min-height: 15px; + a{ + display: block; + padding: 0.5em 0.65em; + text-decoration: none; + &:hover{ + text-decoration: underline; + } + .toolMenuIcon{ + vertical-align: sub; + margin: 0; + } + } + .#{$namespace}sitesNav__submenuitem-icon { + display: inline-block; + vertical-align: top; + } + .#{$namespace}sitesNav__submenuitem-title { + margin-bottom: 2px; + display: inline-block; + width: 80%; + } + } + &.is-visible{ + display: block; + } + &.is-hidden{ + display: none; + } + } &.is-selected{ a{ background: $primary-color; color: $background-color; //padding: 0.8em 0.9em 0.8em 0.9em; - .#{$namespace}sitesNav__dropdown{ - display: none; - } } ul{ - background: $background-color; + background: $primary-color; } } &.#{$namespace}sitesNav__menuitem--myworkspace{ @@ -593,6 +634,20 @@ body.is-logged-out{ } } } + + &.dropdown-is-visible { + > a{ + border-bottom: 1px solid $background-color; + z-index: 150; + } + } + + &.dropdown-is-visible.is-selected { + > a{ + border-bottom: 1px solid $primary-color; + } + } + } } &.opened{ @@ -609,6 +664,13 @@ body.is-logged-out{ } } +.sitenav-dropdown-overlay{ + height: 100%; + position: fixed; + width: 100%; + z-index: 20; +} + body.is-logged-out{ #linkNav{ ul{