Skip to content

Commit

Permalink
SAK-30530 Sakai Favorite Site Tool Dropdown Menu Enhancements (sakaip…
Browse files Browse the repository at this point in the history
…roject#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
  • Loading branch information
marktriggs authored and ottenhoff committed Apr 29, 2016
1 parent d9403bd commit d7d6f51
Show file tree
Hide file tree
Showing 2 changed files with 231 additions and 144 deletions.
301 changes: 163 additions & 138 deletions reference/library/src/morpheus-master/js/src/sakai.morpheus.subnav.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 = "<ul class=\"Mrphs-sitesNav__submenu\" role=\"menu\">";
var maxToolsInt = parseInt($PBJQ('#linkNav').attr('data-max-tools-int'));
var maxToolsText = $PBJQ('#linkNav').attr('data-max-tools-anchor');
var goToSite = '<li class=\"Mrphs-sitesNav__submenuitem\"><a role=\"menuitem\" href=\"' + portal.portalPath + '/site/' + siteId + '\" title=\"' + maxToolsText + '\"><span class=\"toolMenuIcon icon-sakai-see-all-tools\"></span>' + maxToolsText + '</a></li>';
var siteURL = '/direct/site/' + siteId + '/pages.json';
var currentSite = window.location.pathname.split('/').pop();

$PBJQ.ajax({
url: siteURL,
dataType: "json",
success: function(data){

$PBJQ.each(data, function(i, item) {

// Check to see if this is the current tool in the site
var isCurrent = "";
if (currentSite == item.tools[0].id) {
isCurrent = " is-current";
}

$PBJQ("ul.Mrphs-sitesNav__menu").each(function(){
if (i <= maxToolsInt) {
var li_template;

if (item.toolpopup) {
var link_attrs = ' role="menuitem" href="{{tool_url}}?sakai.popup=yes" title="{{item_title}}" onclick="window.open(\'{{item_toolpopupurl}}\');"';
li_template = '<li class="Mrphs-sitesNav__submenuitem" >' +
'<a class="Mrphs-sitesNav__submenuitem-icon"' + link_attrs + '><span class="toolMenuIcon icon-{{icon}}"></span></a>' +
'<a class="Mrphs-sitesNav__submenuitem-title"' + link_attrs + '>{{item_title}}</a>' +
'</li>';
} else {
var link_attrs = ' role="menuitem" href="{{tool_url}}" title="{{item_title}}"';

li_template = '<li class="Mrphs-sitesNav__submenuitem{{is_current}}">' +
'<a class="Mrphs-sitesNav__submenuitem-icon"' + link_attrs + '><span class="toolMenuIcon icon-{{icon}}"></span></a>' +
'<a class="Mrphs-sitesNav__submenuitem-title"' + link_attrs + '>{{item_title}}</a>' +
'</li>';
}

// Add an escape key handler to slide the page menu up
$PBJQ(this).keydown(function(e) {
if (e.keyCode == 27) {
$PBJQ(this).parent().children('a').focus();
$PBJQ(this).toggleClass('is-visible');
}
});
navsubmenu += (li_template
.replace(/{{tool_url}}/g, item.tools[0].url)
.replace(/{{item_title}}/g, item.title)
.replace(/{{item_toolpopupurl}}/g, item.toolpopupurl)
.replace(/{{icon}}/g, item.tools[0].toolId.replace(/\./gi, '-'))
.replace(/{{is_current}}/g, isCurrent));
}
});

$PBJQ(this).children('li:last').addClass('is-last-item')
});
if((data.length - 1) > maxToolsInt) {
navsubmenu += goToSite
}

$PBJQ("ul.Mrphs-sitesNav__menu > li").mouseleave(function(){
$PBJQ(this).find('ul').toggleClass('is-visible');
});
navsubmenu += "</ul>"

fixTopNav = function(e) {
navsubmenu = $(navsubmenu);

if (e.keyCode == 40) { // downarrow

e.preventDefault();
$PBJQ('#selectSite').hide();
//$PBJQ('.nav-submenu').hide();
// Trigger click on the drop <span>, 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 <span>, 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 = "<ul class=\"Mrphs-sitesNav__submenu is-visible\" role=\"menu\">";
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 = '<li class=\"Mrphs-sitesNav__submenuitem\"><a role=\"menuitem\" href=\"' + portal.portalPath + '/site/' + siteId + '\" title=\"' + maxToolsText + '\"><span class=\"toolMenuIcon icon-sakai-see-all-tools\"></span>' + maxToolsText + '</a></li>';
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 = $('<div class="sitenav-dropdown-overlay" />');

if (i <= maxToolsInt) {

if (item.toolpopup) {
navsubmenu = navsubmenu + '<li class=\"Mrphs-sitesNav__submenuitem\" ><a role=\"menuitem\" href=\"' + item.tools[0].url + "?sakai.popup=yes\" title=\"" + item.title + "\" onclick=\"window.open('" + item.toolpopupurl + "');\"><span class=\"toolMenuIcon icon-" + item.tools[0].toolId.replace(/\./gi, '-') + "\"></span>" + item.title + "</a></li>";
overlay.on('click', function (e) {
closeAllDropdownMenus();
});

} else if (item.tools.length >= 1) { // changed from item.tools.length === 1
$('body').prepend(overlay);

// Check to see if this is the current tool in the site
var isCurrent = "";
if (currentSite == item.tools[0].id) {
var isCurrent = " is-current";
}
navsubmenu = navsubmenu + '<li class=\"Mrphs-sitesNav__submenuitem' + isCurrent + '\"><a role=\"menuitem\" href=\"' + item.tools[0].url + "\" title=\"" + item.title + "\"><span class=\"toolMenuIcon icon-" + item.tools[0].toolId.replace(/\./gi, '-') + "\"></span>" + item.title + "</a></li>";
}
}
});
dropdownArrow.removeData('clicked');
};

if((data.length - 1) > maxToolsInt) {
navsubmenu = navsubmenu + goToSite
}

navsubmenu = navsubmenu + "</ul>"

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();
}
});
}
Loading

0 comments on commit d7d6f51

Please sign in to comment.