{event name='javascriptLanguageImport'}
});
-
- $(function() {
- console.time('wcf');
- //WCF.TabMenu.init();
- //WCF.System.FlexibleMenu.init();
- console.timeEnd('wcf');
- });
</script>
<script data-relocate="true" src="{@$__wcf->getPath()}js/require.config.js"></script>
baseUrl: '{@$__wcf->getPath()}js'
});
- /*require(function(require) {
- var ui = require('WCF/UI');
-
- console.debug(ui);
- });*/
-
define('jQuery', [], function() { return window.jQuery; });
$.holdReady(true);
require(['WoltLab/WCF/Bootstrap'], function(bootstrap) {
bootstrap.setup();
});
- /*
- require(['WoltLab/WCF/Date/Time/Relative', 'UI/SimpleDropdown'], function(relative, dropdown) {
- relative.init();
-
- console.time('wcfNew');
- dropdown.setup();
- console.timeEnd('wcfNew');
-
- $.holdReady(false);
- });
- */
- /*
- require(function(require) {
- var core = require('WoltLab/WCF/Core');
-
- core.Init();
- });
-
- require(['WoltLab/WCF/Core'], function(core) {
- core.Init();
- });*/
</script>
{if ENABLE_DEBUG_MODE}
* Initializes all TabMenus
*/
init: function() {
- require(['WoltLab/WCF/UI/TabMenu'], function(tabMenu) {
- tabMenu.init();
+ require(['WoltLab/WCF/UI/TabMenu'], function(UITabMenu) {
+ UITabMenu.setup();
});
return;
* Provides flexible dropdowns for tab-based menus.
*/
WCF.System.FlexibleMenu = {
- /**
- * list of containers
- * @var object<jQuery>
- */
- _containers: { },
-
- /**
- * list of registered container ids
- * @var array<string>
- */
- _containerIDs: [ ],
-
- /**
- * list of dropdowns
- * @var object<jQuery>
- */
- _dropdowns: { },
-
- /**
- * list of dropdown menus
- * @var object<jQuery>
- */
- _dropdownMenus: { },
-
- /**
- * list of hidden status for containers
- * @var object<boolean>
- */
- _hasHiddenItems: { },
-
- /**
- * true if menus are currently rebuilt
- * @var boolean
- */
- _isWorking: false,
-
- /**
- * list of tab menu items per container
- * @var object<jQuery>
- */
- _menuItems: { },
-
/**
* Initializes the WCF.System.FlexibleMenu class.
*/
- init: function() {
- // register .mainMenu and .navigationHeader by default
- this.registerMenu('mainMenu');
- this.registerMenu($('.navigationHeader:eq(0)').wcfIdentify());
-
- this._registerTabMenus();
-
- $(window).resize($.proxy(this.rebuildAll, this));
- WCF.DOMNodeInsertedHandler.addCallback('WCF.System.FlexibleMenu', $.proxy(this._registerTabMenus, this));
- },
-
- /**
- * Registers tab menus.
- */
- _registerTabMenus: function() {
- // register tab menus
- $('.tabMenuContainer:not(.jsFlexibleMenuEnabled), .messageTabMenu:not(.jsFlexibleMenuEnabled)').each(function(index, tabMenuContainer) {
- var $navigation = $(tabMenuContainer).addClass('jsFlexibleMenuEnabled').children('nav');
- if ($navigation.length && $navigation.find('> ul:eq(0) > li').length) {
- WCF.System.FlexibleMenu.registerMenu($navigation.wcfIdentify());
- }
- });
- },
+ init: function() { /* does nothing */ },
/**
* Registers a tab-based menu by id.
* @param string containerID
*/
registerMenu: function(containerID) {
- var $container = $('#' + containerID);
- if (!$container.length) {
- console.debug("[WCF.System.FlexibleMenu] Unable to find container identified by '" + containerID + "', aborting.");
- return;
- }
-
- this._containerIDs.push(containerID);
- this._containers[containerID] = $container;
- this._menuItems[containerID] = $container.find('> ul:eq(0) > li');
- this._dropdowns[containerID] = $('<li class="dropdown jsFlexibleMenuDropdown"><a class="icon icon16 icon-list" /></li>').data('containerID', containerID).click($.proxy(this._click, this));
- this._dropdownMenus[containerID] = $('<ul class="dropdownMenu" />').appendTo(this._dropdowns[containerID]);
- this._hasHiddenItems[containerID] = false;
-
- this.rebuild(containerID);
-
- WCF.Dropdown.initDropdown(this._dropdowns[containerID].children('a'));
- },
-
- /**
- * Rebuilds all registered containers.
- */
- rebuildAll: function() {
- if (this._isWorking) {
- return;
- }
-
- this._isWorking = true;
-
- for (var $i = 0, $length = this._containerIDs.length; $i < $length; $i++) {
- this.rebuild(this._containerIDs[$i]);
- }
-
- this._isWorking = false;
+ require(['WoltLab/WCF/UI/FlexibleMenu'], function(UIFlexibleMenu) {
+ UIFlexibleMenu.register(containerID);
+ });
},
/**
* @param string containerID
*/
rebuild: function(containerID) {
- if (!this._containers[containerID]) {
- console.debug("[WCF.System.FlexibleMenu] Cannot rebuild unknown container identified by '" + containerID + "'");
- return;
- }
-
- var $container = this._containers[containerID];
-
- // hide all items
- var $menuItems = this._menuItems[containerID].hide();
-
- // the active item must always be visible
- var $activeItem = $menuItems.filter('.active, .ui-state-active').show();
-
- // insert dropdown for calculation purposes
- if (!this._hasHiddenItems[containerID]) {
- this._dropdowns[containerID].appendTo($container.children('ul:eq(0)'));
- }
- var $dropdownWidth = this._dropdowns[containerID].outerWidth(true);
-
- // get maximum width
- var $parent = $container.parent();
- var $maximumWidth = $parent.innerWidth();
-
- // exclude padding
- $maximumWidth -= $parent.cssAsNumber('padding-left') + $parent.cssAsNumber('padding-right');
-
- // substract margins and paddings from the container itself
- $maximumWidth -= $container.cssAsNumber('margin-left') + $container.cssAsNumber('margin-right');
- $maximumWidth -= $container.cssAsNumber('padding-left') + $container.cssAsNumber('padding-right');
-
- // substract paddings from the actual list
- $maximumWidth -= $container.children('ul:eq(0)').cssAsNumber('padding-left') + $container.children('ul:eq(0)').cssAsNumber('padding-right');
-
- // the active item must always be visible, substract its width
- $maximumWidth -= $activeItem.outerWidth(true);
-
- // show items until maximum width is exceeded
- this._hasHiddenItems[containerID] = false;
- for (var $i = 0; $i < $menuItems.length; $i++) {
- var $item = $($menuItems[$i]);
-
- // ignore active item because it is already visible
- if ($item.hasClass('active') || $item.hasClass('ui-state-active')) {
- continue;
- }
-
- var $width = $item.outerWidth(true);
- if ($maximumWidth - $width > 0) {
- $maximumWidth -= $width;
- $item.show();
- }
- else {
- // check if dropdown no longer fits in
- if ($maximumWidth < $dropdownWidth) {
- // hide previous item to clear up some space for the dropdown unless it is the active item
- var $prev = $item.prev();
- if ($prev.hasClass('active') || $prev.hasClass('ui-state-active')) {
- $prev.prev().hide();
- }
- else {
- $prev.hide();
- }
- }
-
- this._hasHiddenItems[containerID] = true;
-
- break;
- }
- }
-
- // rebuild dropdown
- if (this._hasHiddenItems[containerID]) {
- this._dropdownMenus[containerID].empty();
- var self = this;
- $menuItems.each($.proxy(function(index, item) {
- if ($(item).is(':visible')) {
- return true;
- }
-
- $('<li>' + $(item).html() + '</li>').data('index', index).appendTo(this._dropdownMenus[containerID]).click(function(event) {
- // forward click to the original item
- var $item = $($menuItems[$(event.currentTarget).data('index')]);
- if ($item[0].tagName === 'A') {
- $item.trigger('click');
- }
- else if ($item[0].tagName === 'LI') {
- $item.find('a').trigger('click');
- }
-
- // prevent links being followed (they are mandatory in jQuery UI's tab menu)
- if ($item.parent().hasClass('ui-tabs-nav')) {
- event.preventDefault();
- }
-
- // force a rebuild to guarantee the active item being visible
- setTimeout(function() {
- self.rebuild(containerID);
- }, 50);
- });
- }, this));
- }
- else {
- // remove dropdown if there are no hidden items
- this._dropdowns[containerID].detach();
- }
+ require(['WoltLab/WCF/UI/FlexibleMenu'], function(UIFlexibleMenu) {
+ UIFlexibleMenu.rebuild(containerID);
+ });
}
};
* @module WoltLab/WCF/Bootstrap
*/
define(
- [ 'favico', 'enquire', 'WoltLab/WCF/Date/Time/Relative', 'UI/SimpleDropdown', 'WoltLab/WCF/UI/Mobile', 'WoltLab/WCF/UI/TabMenu'],
- function(favico, enquire, relativeTime, simpleDropdown, uiMobile, TabMenu)
+ [ 'favico', 'enquire', 'WoltLab/WCF/Date/Time/Relative', 'UI/SimpleDropdown', 'WoltLab/WCF/UI/Mobile', 'WoltLab/WCF/UI/TabMenu', 'WoltLab/WCF/UI/FlexibleMenu'],
+ function(favico, enquire, relativeTime, simpleDropdown, uiMobile, TabMenu, FlexibleMenu)
{
+ "use strict";
+
window.Favico = favico;
window.enquire = enquire;
simpleDropdown.setup();
uiMobile.setup();
TabMenu.setup();
+ FlexibleMenu.setup();
$.holdReady(false);
}
- }
+ };
return new Bootstrap();
});
function(el, tagName) { return el.nodeName === tagName; }
];
+ var _children = function(el, type, value) {
+ var children = [];
+
+ for (var i = 0; i < el.childElementCount; i++) {
+ if (_probe[type](el.children[i], value)) {
+ children.push(el.children[i]);
+ }
+ }
+
+ return children;
+ };
+
var _parent = function(el, type, value) {
el = el.parentNode;
*/
function DOMTraverse() {};
DOMTraverse.prototype = {
+ /**
+ * Examines child elements and returns all children matching the given selector.
+ *
+ * @param {Element} el element
+ * @param {string} selector CSS selector to match child elements against
+ * @return {array<Element>} list of children matching the selector
+ */
+ childrenBySel: function(el, selector) {
+ return _children(el, SELECTOR, selector);
+ },
+
+ /**
+ * Examines child elements and returns all children that have the given CSS class set.
+ *
+ * @param {Element} el element
+ * @param {string} className CSS class name
+ * @return {array<Element>} list of children with the given class
+ */
+ childrenByClass: function(el, className) {
+ return _children(el, CLASS_NAME, className);
+ },
+
+ /**
+ * Examines child elements and returns all children which equal the given tag.
+ *
+ * @param {Element} el element
+ * @param {string} tagName element tag name
+ * @return {array<Element>} list of children equaling the tag name
+ */
+ childrenByTag: function(el, tagName) {
+ return _children(el, TAG_NAME, tagName);
+ },
+
/**
* Examines parent nodes and returns the first parent that matches the given selector.
*
return {
top: rect.top + document.body.scrollTop,
left: rect.left + document.body.scrollLeft
- }
+ };
},
/**
* Applies a list of CSS properties to an element.
*
* @param {Element} el element
- * @param {Object<string, mixed} styles list of CSS styles
+ * @param {Object<string, mixed>} styles list of CSS styles
*/
setStyles: function(el, styles) {
for (var property in styles) {
el.style.setProperty(property, styles[property]);
}
}
+ },
+
+ /**
+ * Returns a style property value as integer.
+ *
+ * The behavior of this method is undefined for properties that are not considered
+ * to have a "numeric" value, e.g. "background-image".
+ *
+ * @param {CSSStyleDeclaration} styles result of window.getComputedStyle()
+ * @param {string} propertyName property name
+ * @return {integer} property value as integer
+ */
+ styleAsInt: function(styles, propertyName) {
+ var value = styles.getPropertyValue(propertyName);
+ if (value === null) {
+ return 0;
+ }
+
+ return parseInt(value);
}
};
--- /dev/null
+/**
+ * Dynamically transforms menu-like structures to handle items exceeding the available width
+ * by moving them into a separate dropdown.
+ *
+ * @author Alexander Ebert
+ * @copyright 2001-2015 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @module WoltLab/WCF/UI/FlexibleMenu
+ */
+define(['Core', 'Dictionary', 'DOM/Traverse', 'DOM/Util', 'UI/SimpleDropdown'], function(Core, Dictionary, DOMTraverse, DOMUtil, SimpleDropdown) {
+ "use strict";
+
+ /**
+ * @constructor
+ */
+ var UIFlexibleMenu = function() {
+ this._containers = new Dictionary();
+ this._dropdowns = new Dictionary();
+ this._dropdownMenus = new Dictionary();
+ this._itemLists = new Dictionary();
+ };
+ UIFlexibleMenu.prototype = {
+ /**
+ * Register default menus and set up event listeners.
+ */
+ setup: function() {
+ if (document.getElementById('mainMenu') !== null) this.register('mainMenu');
+ var navigationHeader = document.querySelector('.navigationHeader');
+ if (navigationHeader !== null) this.register(DOMUtil.identify(navigationHeader));
+
+
+ window.addEventListener('resize', this.rebuildAll.bind(this));
+ WCF.DOMNodeInsertedHandler.addCallback('WoltLab/WCF/UI/FlexibleMenu', this.registerTabMenus.bind(this));
+ },
+
+ /**
+ * Registers a menu by element id.
+ *
+ * @param {string} containerId element id
+ */
+ register: function(containerId) {
+ var container = document.getElementById(containerId);
+ if (container === null) {
+ throw "Expected a valid element id, '" + containerId + "' does not exist.";
+ }
+
+ if (this._containers.has(containerId)) {
+ return;
+ }
+
+ var lists = DOMTraverse.childrenByTag(container, 'UL');
+ if (!lists.length) {
+ throw "Expected an <ul> element as child of container '" + containerId + "'.";
+ }
+
+ this._containers.set(containerId, container);
+ this._itemLists.set(containerId, lists[0]);
+
+ this.rebuild(containerId);
+ },
+
+ /**
+ * Registers tab menus.
+ */
+ registerTabMenus: function() {
+ var tabMenus = document.querySelectorAll('.tabMenuContainer:not(.jsFlexibleMenuEnabled), .messageTabMenu:not(.jsFlexibleMenuEnabled)');
+ for (var i = 0, length = tabMenus.length; i < length; i++) {
+ var tabMenu = tabMenus[i];
+ var navs = DOMTraverse.childrenByTag(tabMenu, 'NAV');
+ if (navs.length !== 0) {
+ tabMenu.classList.add('jsFlexibleMenuEnabled');
+ this.register(DOMUtil.identify(navs[0]));
+ }
+ }
+ },
+
+ /**
+ * Rebuilds all menus, e.g. on window resize.
+ */
+ rebuildAll: function() {
+ this._containers.forEach((function(container, containerId) {
+ this.rebuild(containerId);
+ }).bind(this));
+ },
+
+ /**
+ * Rebuild the menu identified by given element id.
+ *
+ * @param {string} containerId element id
+ */
+ rebuild: function(containerId) {
+ var container = this._containers.get(containerId);
+ if (container === null) {
+ throw "Expected a valid element id, '" + containerId + "' is unknown.";
+ }
+
+ var styles = window.getComputedStyle(container);
+
+ var availableWidth = container.parentNode.clientWidth;
+ availableWidth -= DOMUtil.styleAsInt(styles, 'margin-left');
+ availableWidth -= DOMUtil.styleAsInt(styles, 'margin-right');
+
+ var list = this._itemLists.get(containerId);
+ var items = DOMTraverse.childrenByTag(list, 'LI');
+ var dropdown = this._dropdowns.get(containerId);
+ var dropdownWidth = 0;
+ if (dropdown !== null) {
+ // show all items for calculation
+ for (var i = 0, length = items.length; i < length; i++) {
+ var item = items[i];
+ if (item.classList.contains('dropdown')) {
+ continue;
+ }
+
+ item.style.removeProperty('display');
+ }
+
+ if (dropdown.parentNode !== null) {
+ dropdownWidth = DOMUtil.outerWidth(dropdown);
+ }
+ }
+
+ var currentWidth = list.scrollWidth - dropdownWidth;
+ var hiddenItems = [];
+ if (currentWidth > availableWidth) {
+ // hide items starting with the last one
+ for (var i = items.length - 1; i >= 0; i--) {
+ var item = items[i];
+
+ // ignore dropdown and active item
+ if (item.classList.contains('dropdown') || item.classList.contains('active') || item.classList.contains('ui-state-active')) {
+ continue;
+ }
+
+ hiddenItems.push(item);
+ item.style.setProperty('display', 'none');
+
+ if (list.scrollWidth < availableWidth) {
+ break;
+ }
+ }
+ }
+
+ if (hiddenItems.length) {
+ var dropdownMenu;
+ if (dropdown === null) {
+ dropdown = document.createElement('li');
+ dropdown.className = 'dropdown jsFlexibleMenuDropdown';
+ var icon = document.createElement('a');
+ icon.className = 'icon icon16 fa-list';
+ dropdown.appendChild(icon);
+
+ dropdownMenu = document.createElement('ul');
+ dropdownMenu.classList.add('dropdownMenu');
+ dropdown.appendChild(dropdownMenu);
+
+ this._dropdowns.set(containerId, dropdown);
+ this._dropdownMenus.set(containerId, dropdownMenu);
+
+ SimpleDropdown.init(icon);
+ }
+ else {
+ dropdownMenu = this._dropdownMenus.get(containerId);
+ }
+
+ if (dropdown.parentNode === null) {
+ list.appendChild(dropdown);
+ }
+
+ // build dropdown menu
+ var fragment = document.createDocumentFragment();
+
+ var self = this;
+ hiddenItems.forEach(function(hiddenItem) {
+ var item = document.createElement('li');
+ item.innerHTML = hiddenItem.innerHTML;
+
+ item.addEventListener('click', (function(event) {
+ event.preventDefault();
+
+ Core.triggerEvent(hiddenItem.querySelector('a'), 'click');
+
+ // force a rebuild to guarantee the active item being visible
+ setTimeout(function() {
+ self.rebuild(containerId);
+ }, 59);
+ }).bind(this));
+
+ fragment.appendChild(item);
+ });
+
+ dropdownMenu.innerHTML = '';
+ dropdownMenu.appendChild(fragment);
+ }
+ else if (dropdown !== null && dropdown.parentNode !== null) {
+ dropdown.parentNode.removeChild(dropdown);
+ }
+ }
+ };
+
+ return new UIFlexibleMenu();
+});
\ No newline at end of file