@eclipse-scout/core
Version:
Eclipse Scout runtime
234 lines (210 loc) • 8.41 kB
text/typescript
/*
* Copyright (c) 2010, 2023 BSI Business Systems Integration AG
*
* This program and the accompanying materials are made
* available under the terms of the Eclipse Public License 2.0
* which is available at https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*/
import {Action, arrays, ComboMenu, EllipsisMenu, InitModelOf, Menu, MenuDestinations, ObjectOrChildModel, scout, Widget} from '../index';
import $ from 'jquery';
export type MenuFilterOptions = {
onlyVisible?: boolean;
enableDisableKeyStrokes?: boolean;
notAllowedTypes?: string | string[];
defaultMenuTypes?: string | string[];
};
export const menus = {
filterAccordingToSelection(prefix: string, selectionLength: number, menuArr: Menu[], destination: MenuDestinations, options?: MenuFilterOptions): Menu[] {
let allowedTypes: string[] = [],
{notAllowedTypes} = options || {};
if (destination === MenuDestinations.MENU_BAR) {
allowedTypes = [prefix + '.EmptySpace', prefix + '.SingleSelection', prefix + '.MultiSelection'];
} else if (destination === MenuDestinations.CONTEXT_MENU) {
allowedTypes = [prefix + '.SingleSelection', prefix + '.MultiSelection'];
} else if (destination === MenuDestinations.HEADER) {
allowedTypes = [prefix + '.Header'];
}
if (allowedTypes.indexOf(prefix + '.SingleSelection') > -1 && selectionLength !== 1) {
arrays.remove(allowedTypes, prefix + '.SingleSelection');
}
if (allowedTypes.indexOf(prefix + '.MultiSelection') > -1 && selectionLength <= 1) {
arrays.remove(allowedTypes, prefix + '.MultiSelection');
}
notAllowedTypes = arrays.ensure(notAllowedTypes);
let fixedNotAllowedTypes = [];
// ensure prefix
prefix = prefix + '.';
notAllowedTypes.forEach(type => {
if (type.slice(0, prefix.length) !== prefix) {
type = prefix + type;
}
fixedNotAllowedTypes.push(type);
});
return menus.filter(menuArr, allowedTypes, $.extend({}, options, {notAllowedTypes: fixedNotAllowedTypes}));
},
/**
* Filters menus that don't match the given types, or in other words: only menus with the given types are returned
* from this method. The visible state is only checked if the parameter onlyVisible is set to true. Otherwise, invisible items are returned and added to the
* menu-bar DOM (invisible, however). They may change their visible state later. If there are any types in notAllowedTypes each menu is checked also against
* these types and if they are matching the menu is filtered.
*/
filter(menuArr: Menu[], types?: string | string[], options?: MenuFilterOptions): Menu[] {
if (!menuArr) {
return;
}
types = arrays.ensure(types);
let {onlyVisible, enableDisableKeyStrokes, notAllowedTypes, defaultMenuTypes} = options || {};
notAllowedTypes = arrays.ensure(notAllowedTypes);
defaultMenuTypes = arrays.ensure(defaultMenuTypes);
let filteredMenus = [], separatorCount = 0;
menuArr.forEach(menu => {
let childMenus = menu.childActions;
if (childMenus.length > 0) {
childMenus = menus.filter(childMenus, types, options);
if (childMenus.length === 0) {
menus._enableDisableMenuKeyStroke(menu, enableDisableKeyStrokes, true);
return;
}
} else if (!menus._checkType(menu, types as string[], defaultMenuTypes) || (notAllowedTypes.length !== 0 && menus._checkType(menu, notAllowedTypes as string[], defaultMenuTypes))) {
// Don't check the menu type for a group
menus._enableDisableMenuKeyStroke(menu, enableDisableKeyStrokes, true);
return;
}
if (onlyVisible && !menu.visible) {
menus._enableDisableMenuKeyStroke(menu, enableDisableKeyStrokes, true);
return;
}
if (menu.separator) {
separatorCount++;
}
menus._enableDisableMenuKeyStroke(menu, enableDisableKeyStrokes, false);
filteredMenus.push(menu);
});
// Ignore menus with only separators
if (separatorCount === filteredMenus.length) {
return [];
}
return filteredMenus;
},
/**
* Makes leading, trailing and duplicate separators invisible or reverts the visibility change if needed.
*/
updateSeparatorVisibility(menuArr: Menu | Menu[]) {
menuArr = arrays.ensure(menuArr);
menuArr = menuArr.filter(menu => menu.visible || menu.separator);
if (menuArr.length === 0) {
return;
}
let hasMenuBefore = false;
let hasMenuAfter = false;
menuArr.forEach((menu: Menu & { visibleOrig: boolean }, i: number) => {
if (menu.ellipsis) {
return;
}
if (!menu.separator) {
hasMenuBefore = true;
return;
}
hasMenuAfter = menuArr[i + 1] && !menuArr[i + 1].separator && !menuArr[i + 1].ellipsis;
// If the separator has a separator next to it, make it invisible
if (!hasMenuBefore || !hasMenuAfter) {
if (menu.visibleOrig === undefined) {
menu.visibleOrig = menu.visible;
menu.setVisible(false);
}
} else if (menu.visibleOrig !== undefined) {
// Revert to original state
menu.setVisible(menu.visibleOrig);
menu.visibleOrig = undefined;
}
});
},
checkType(menu: Menu, types: string | string[], defaultMenuTypes: string | string[] = []): boolean {
types = arrays.ensure(types);
if (menu.childActions.length > 0) {
let childMenus = menus.filter(menu.childActions, types, {defaultMenuTypes});
return childMenus.length > 0;
}
return menus._checkType(menu, types, defaultMenuTypes);
},
/** @internal */
_enableDisableMenuKeyStroke(menu: Menu, activated: boolean, exclude: boolean) {
if (activated) {
menu.excludedByFilter = exclude;
}
},
/**
* Checks the type of menu. Don't use this for menu groups.
* @internal
*/
_checkType(menu: Menu, types: string[], defaultMenuTypes: string | string[] = []): boolean {
if (!types || types.length === 0) {
return false;
}
let menuTypes = arrays.ensure(menu.menuTypes);
defaultMenuTypes = arrays.ensure(defaultMenuTypes);
if (menuTypes.length === 0) {
menuTypes = defaultMenuTypes;
}
for (let j = 0; j < types.length; j++) {
if (menuTypes.indexOf(types[j]) > -1) {
return true;
}
}
return false;
},
createEllipsisMenu(options: InitModelOf<EllipsisMenu>): EllipsisMenu {
return scout.create(EllipsisMenu, options);
},
moveMenuIntoEllipsis(menu: Menu, ellipsis: EllipsisMenu) {
menu.remove();
menu._setOverflown(true);
menu.overflowMenu = ellipsis;
let menusInEllipsis = ellipsis.childActions.slice();
menusInEllipsis.unshift(menu); // add as first element
ellipsis.setChildActions(menusInEllipsis);
},
removeMenuFromEllipsis(menu: Menu, $parent?: JQuery) {
menu._setOverflown(false);
menu.overflowMenu = null;
if (!menu.rendered) {
menu.render($parent);
}
},
/**
* If the given actions contain a {@link ComboMenu}, the resulting array contains the child actions of the combo menu instead of the combo menu itself.
*/
flatTopLevelActions<TAction extends Action>(actions: TAction[]): TAction[] {
return arrays.flatMap(actions, item => {
if (item instanceof ComboMenu) {
return item.childActions;
}
return [item];
});
},
/**
* Appends the given menus to the existing menus of the menu owner and calls {@link MenuOwner.setMenus} to set the new list of menus.
*/
insertMenus(menuOwner: MenuOwner, menusToInsert: ObjectOrChildModel<Menu>[]) {
menusToInsert = arrays.ensure(menusToInsert);
if (menusToInsert.length === 0) {
return;
}
let menus = menuOwner.menus as ObjectOrChildModel<Menu>[];
menuOwner.setMenus(menus.concat(menusToInsert));
},
/**
* Removes the given menus from the existing menus of the menu owner and calls {@link MenuOwner.setMenus} to set the new list of menus.
*/
deleteMenus(menuOwner: MenuOwner, menusToDelete: Menu[]) {
menusToDelete = arrays.ensure(menusToDelete);
if (menusToDelete.length === 0) {
return;
}
let menus = arrays.diff(menuOwner.menus, menusToDelete);
menuOwner.setMenus(menus);
}
};
export type MenuOwner = Widget & { menus: Menu[]; setMenus: (menus: ObjectOrChildModel<Menu>[]) => void };