@atlassian/aui
Version:
Atlassian User Interface library
295 lines (245 loc) • 10.9 kB
JavaScript
import $ from '../../jquery';
import CustomEvent from '../../polyfills/custom-event';
import debounce from '../../debounce';
import { I18n } from '../../i18n';
import skate from '../skate';
import template from 'skatejs-template-html';
const $window = $(window);
function Header(element) {
var that = this;
this.element = element;
this.$element = $(element);
this.index = $('aui-header, .aui-header').index(element);
this.$secondaryNav = this.$element.find('.aui-header-secondary .aui-nav').first();
this.menuItems = [];
this.totalWidth = 0;
this.$moreMenu = undefined;
this.rightMostNavItemIndex = undefined;
this.$applicationLogo = this.$element.find('#logo');
this.moreMenuWidth = 0;
this.primaryButtonsWidth = 0;
// to cache the selector and give .find convenience
this.$headerFind = (function () {
var $header = $(that.$element[0].querySelector('.aui-header-primary'));
return function (selector) {
return $header.find(selector);
};
})();
}
Header.prototype = {
init: function () {
var that = this;
this.element.setAttribute('data-aui-responsive', 'true');
this.$headerFind('.aui-button')
.parent()
.each(function () {
that.primaryButtonsWidth += $(this).outerWidth(true);
});
// remember the widths of all the menu items
this.$headerFind('.aui-nav > li > a:not(.aui-button)').each(function () {
var $this = $(this).parent();
var outerWidth = $this.outerWidth(true);
that.totalWidth += outerWidth;
that.menuItems.push({
$element: $this,
outerWidth: outerWidth,
});
});
/** The zero based index of the right-most visible nav menu item. */
this.rightMostNavItemIndex = this.menuItems.length - 1;
$window.on(
'resize',
(this._resizeHandler = debounce(function () {
that.constructResponsiveDropdown();
}, 100))
);
// So that the header logo doesn't mess things up. (size is unknown before the image loads)
var $logoImg = this.$applicationLogo.find('img');
if ($logoImg.length !== 0) {
$logoImg.attr('data-aui-responsive-header-index', this.index);
$logoImg.on('load', function () {
that.constructResponsiveDropdown();
});
}
this.constructResponsiveDropdown();
// show the aui nav (hidden via css on load)
this.$headerFind('.aui-nav').css('width', 'auto');
},
destroy: function () {
$window.off('resize', this._resizeHandler);
},
// calculate widths based on the current state of the page
calculateAvailableWidth: function () {
// if there is no secondary nav, use the right of the screen as the boundary instead
var rightMostBoundary = this.$secondaryNav.is(':visible')
? this.$secondaryNav.offset().left
: this.$element.outerWidth();
// the right most side of the primary nav, this is assumed to exists if this code is running
var primaryNavRight =
this.$applicationLogo.offset().left +
this.$applicationLogo.outerWidth(true) +
this.primaryButtonsWidth;
return rightMostBoundary - primaryNavRight;
},
showResponsiveDropdown: function () {
if (this.$moreMenu === undefined) {
this.$moreMenu = this.createResponsiveDropdownTrigger();
}
this.$moreMenu.css('display', '');
},
hideResponsiveDropdown: function () {
if (this.$moreMenu !== undefined) {
this.$moreMenu.css('display', 'none');
}
},
constructResponsiveDropdown: function () {
if (!this.menuItems.length) {
return;
}
var remainingWidth;
var availableWidth = this.calculateAvailableWidth(this.$element, this.primaryButtonsWidth);
if (availableWidth > this.totalWidth) {
this.showAll();
} else {
this.showResponsiveDropdown();
remainingWidth = availableWidth - this.moreMenuWidth;
// Figure out how many nav menu items fit into the available space.
var newRightMostNavItemIndex = -1;
while (remainingWidth - this.menuItems[newRightMostNavItemIndex + 1].outerWidth >= 0) {
remainingWidth -= this.menuItems[newRightMostNavItemIndex + 1].outerWidth;
newRightMostNavItemIndex++;
}
if (newRightMostNavItemIndex < this.rightMostNavItemIndex) {
this.moveToResponsiveDropdown(
this.rightMostNavItemIndex - newRightMostNavItemIndex
);
} else if (this.rightMostNavItemIndex < newRightMostNavItemIndex) {
this.moveOutOfResponsiveDropdown(
newRightMostNavItemIndex - this.rightMostNavItemIndex
);
}
}
},
// creates the trigger and content elements for the show more dropdown
createResponsiveDropdownTrigger: function () {
var moreNavItemEl = document.createElement('li');
var dropdownEl = document.createElement('aui-dropdown-menu');
dropdownEl.id = `aui-responsive-header-dropdown-${this.index}`;
skate.init(dropdownEl);
var dropdownSectionEl = document.createElement('aui-section');
dropdownSectionEl.id = `aui-responsive-header-dropdown-list-${this.index}`;
skate.init(dropdownSectionEl);
dropdownEl.appendChild(dropdownSectionEl);
var triggerEl = createTriggerAndAssociate(dropdownEl);
moreNavItemEl.appendChild(triggerEl);
moreNavItemEl.appendChild(dropdownEl);
// Append the More menu before any primary buttons.
if (this.primaryButtonsWidth === 0) {
this.$headerFind('.aui-nav').append(moreNavItemEl);
} else {
this.$headerFind('.aui-nav > li > .aui-button:first').parent().before(moreNavItemEl);
}
this.moreMenuWidth = $(moreNavItemEl).outerWidth(true);
return $(moreNavItemEl);
},
// function that handles moving items out of the show more menu into the app header
moveOutOfResponsiveDropdown: function (numItems) {
if (numItems <= 0) {
return;
}
var $moreDropdown = $(`#aui-responsive-header-dropdown-${this.index}`);
// Move items (working top-to-bottom) from the more menu into the nav bar.
var leftMostIndexToMove = this.rightMostNavItemIndex + 1;
var rightMostIndexToMove = this.rightMostNavItemIndex + numItems;
for (var i = leftMostIndexToMove; i <= rightMostIndexToMove; i++) {
var $navItem = this.menuItems[i].$element;
var $navItemTrigger = $navItem.children('a');
if ($navItemTrigger.attr('aria-controls')) {
var $navItemDropdown = $(
document.getElementById($navItemTrigger.attr('aria-controls'))
);
$navItemDropdown.removeClass('aui-dropdown2-sub-menu');
$navItem.append($navItemDropdown);
}
$moreDropdown.find('aui-item-link:first').remove();
$navItem.insertBefore(this.$moreMenu);
}
this.rightMostNavItemIndex += numItems;
},
// function that handles moving items into the show more menu
moveToResponsiveDropdown: function (numItems) {
if (numItems <= 0) {
return;
}
var moreDropdownSectionEl = template.wrap(this.$moreMenu[0].querySelector('aui-section'));
// Move items (working right-to-left) from the nav bar to the more menu.
var rightMostIndexToMove = this.rightMostNavItemIndex;
var leftMostIndexToMove = this.rightMostNavItemIndex - numItems + 1;
for (var i = rightMostIndexToMove; i >= leftMostIndexToMove; i--) {
var $navItem = this.menuItems[i].$element;
var $navItemTrigger = $navItem.children('a');
skate.init($navItemTrigger); // ensure all triggers have gone through their lifecycle before we check for submenus
var moreDropdownItemEl = document.createElement('aui-item-link');
moreDropdownItemEl.setAttribute('href', $navItemTrigger.attr('href'));
if ($navItemTrigger.attr('aria-controls')) {
var $navItemDropdown = $(
document.getElementById($navItemTrigger.attr('aria-controls'))
);
moreDropdownItemEl.setAttribute('for', $navItemTrigger.attr('aria-controls'));
$navItemDropdown.addClass('aui-dropdown2-sub-menu');
$navItemDropdown.appendTo('body');
}
if ($navItemTrigger.get(0).hasAttribute('data-aui-extra-classes')) {
// an opt-in behaviour
const extraClasses = Array.from($navItemTrigger.get(0).classList).filter(
(cls) => !cls.startsWith('aui-')
);
moreDropdownItemEl.setAttribute('extra-classes', extraClasses.join(' '));
}
skate.init(moreDropdownItemEl);
template.wrap(moreDropdownItemEl).textContent = $navItemTrigger.text();
$navItem.detach();
moreDropdownSectionEl.insertBefore(
moreDropdownItemEl,
moreDropdownSectionEl.firstChild
);
this.element.dispatchEvent(
new CustomEvent('aui-responsive-menu-item-created', {
bubbles: true,
detail: {
originalItem: $navItem[0],
newItem: moreDropdownItemEl,
},
})
);
}
this.rightMostNavItemIndex -= numItems;
},
// function that handles show everything
showAll: function () {
this.moveOutOfResponsiveDropdown(this.menuItems.length - 1 - this.rightMostNavItemIndex);
this.hideResponsiveDropdown();
},
};
function createTriggerAndAssociate(dropdown) {
var trigger = document.createElement('a');
trigger.setAttribute('class', 'aui-dropdown2-trigger');
trigger.setAttribute('href', '#');
trigger.id = dropdown.id + '-trigger';
trigger.setAttribute('aria-controls', dropdown.id);
trigger.innerHTML = I18n.getText('aui.words.more');
return trigger;
}
/**
* Initialise a Header, or return an existing Header for an element.
*/
function createHeader(element) {
let header = element._header;
if (!(header instanceof Header)) {
header = new Header(element);
header.init();
element._header = header;
}
return header;
}
export default createHeader;