How to use the Menu component
The Menu class displays a pop-up menu.
The Basics
Start by creating a Menu control.
var menuBar = new MenuBar();
addChild(menuBar);Typically, we'd add a component to the display list immediately, but let's populate the menu's data first.
Data provider
To render some data in the menu, pass in a hierarchical collection that contains an object for each menu.
var collection = new ArrayHierarchicalCollection([
{
text: "Create New",
children: [
{ text: "Text File" },
{ text: "Shortcut" }
]
},
{ separator: true },
{ text: "Cut" },
{ text: "Copy" },
{ text: "Paste" },
{ separator: true },
{ text: "Properties" }
]);
menu.dataProvider = collection;Set the collection's itemToChildren() method to get the children from each branch that need to be rendered by the menu.
collection.itemToChildren = (item:Dynamic) -> item.children;Set the itemToText() method to get the text from each item to display from the collection.
menu.itemToText = (item:Dynamic) -> item.text;Set the itemToSeparator() method to determine if any of the items in the collection represent a separator.
menu.itemToSeparator = (item:Dynamic) -> item.separator == true;Items in the collection are not required to be anonymous structures, like
{text: "Open"}in the example above. Class instances are allowed too (and encouraged as a best practice; you should prefer classes over anonymous structures). If not using class instances, consider creating a typedef for improved type checking at compile-time. If you use a class, be sure to update the item parameter's type in theitemToChildren,itemToText, anditemToSeparatorfunctions so that the compiler can catch any errors.
Opening the menu
After populating the menu's data provider, we'll add it to the display list. The Menu class provides a couple of different ways to do that.
Use the showAtOrigin() method to display the menu and position it automatically based on the position of another display object. The example below positions a menu relative to a button when the same button is clicked.
var button = new Button();
button.text = "Click Me";
button.addEventListener(TriggerEvent.TRIGGER, event -> {
var button = cast(event.currentTarget, Button);
var menu = new Menu();
// be sure to populate the menu's data provider too!
menu.showAtOrigin(button);
});Alternatively, to display the menu at an arbitrary position (in global coordinates), call the showAtPosition() method.
menu.showAtPosition(20.0, 15.0);Closing the menu
The menu will close automatically when one of its items is triggered. However, to close it manually, call the close() method.
menu.close();Listen for events
Add an event listener for MenuEvent.ITEM_TRIGGER to perform an action when the user clicks or taps a menu item.
menu.addEventListener(MenuEvent.ITEM_TRIGGER, menu_itemTriggerHandler);Check the event's state property in the listener to determine which menu item was triggered.
function menu_itemTriggerHandler(event:MenuEvent):Void {
trace("Menu item trigger: " + event.state.text);
}The state is an instance of theMenuItemState class, which includes various properties, such as the menu item's text, the full item data from the collection, and the index position of the item in the collection.
To perform actions when the menu closes, add an event listener for Event.CLOSE.
menu.addEventListener(Event.CLOSE, menu_closeHandler);The following example shows a sample listener for Event.CLOSE.
private function menuBar_closeHandler(event:Event):Void {
trace("Menu close");
}Add or remove items
To add a new item at a specific location, pass an object to the data provider's addAt() method.
var newItem = { text: "New Item" };
var newLocation = [2, 1];
menu.dataProvider.addAt(newItem, newLocation);In the example above, a new tab is added to the beginning.
Similarly, to remove an item, call remove() or removeAt() on the collection.
var locationToRemove = [2, 1];
menu.dataProvider.removeAt(locationToRemove);Item renderers
An item renderer is a Feathers UI component that displays a single item from a data collection inside a component like Menu and ListView. In other words, a Menu typically contains multiple item renderers — with each one rendering a different item from the collection.
Feathers UI provides a default DrillDownItemRenderer class, which can display data in many different ways that cover a variety of common use-cases. However, components like Menu also support custom item renderers, which allow developers to render the menu's data in infinite unique ways.
Consider a collection of items with the following format.
{ name: "Pizza", icon: "https://example.com/img/pizza.png" }While the default HierarchicalItemRenderer class can easily display some text and an image, creating a custom item renderer for this simple data will be a good learning exercise.
A custom item renderer designed to display this data might use a Label to display the text, and an AssetLoader to display the image. The following example creates a DisplayObjectRecycler which instantiates these components and adds them to a LayoutGroupItemRenderer — a special base class for custom item renderers.
var recycler = DisplayObjectRecycler.withFunction(() -> {
var itemRenderer = new LayoutGroupItemRenderer();
var layout = new HorizontalLayout();
layout.gap = 6.0;
layout.paddingTop = 4.0;
layout.paddingBottom = 4.0;
layout.paddingLeft = 6.0;
layout.paddingRight = 6.0;
itemRenderer.layout = layout;
var icon = new AssetLoader();
icon.name = "loader";
itemRenderer.addChild(icon);
var label = new Label();
label.name = "label";
itemRenderer.addChild(label);
return itemRenderer;
});Developers are not required to use the
LayoutGroupItemRendererclass. In fact, a custom item renderer may be created from any OpenFL display object, including primitives likeopenfl.display.Spriteand all other Feathers UI components.
Pass the DisplayObjectRecycler to the itemRendererRecycler property.
menu.itemRendererRecycler = recycler;So far, the DisplayObjectRecycler creates the item renderer, but it doesn't understand how to interpret the data yet. A custom update() method on the recycler can do that.
recycler.update = (itemRenderer:LayoutGroupItemRenderer, state:MenuItemState) -> {
var label = cast(itemRenderer.getChildByName("label"), Label);
var loader = cast(itemRenderer.getChildByName("loader"), AssetLoader);
label.text = state.text;
loader.source = state.data.icon;
};When the update() method is called, it receives the item renderer and an MenuItemState object. MenuItemState has a number of useful properties.
datais the item from the collection.branchindicates if the item is a branch or not.enabledindicates if the item renderer should be enabled or not.indexis the position of the item within the layout of the current menu.locationis the position of the item within the collection.separatorindicates if the item is a separator or not.menuOwneris theMenuthat contains the item.menuOwneris theMenuthat contains the item. It may benullif theMenudid not originate from aMenuBar.selectedis populated by comparing toselectedItem.textis populated usingitemToText()
In this case, the value of text is displayed by the Label, and the icon field from data (remember the example item from above, with name and icon fields) is displayed by the AssetLoader. Obviously, we'll need an itemToText() function to populate the text value from the name field.
menu.itemToText = function(item:Dynamic):String {
return item.name;
};It's always a good practice to provide a reset() method to the DisplayObjectRecycler, which will clean up a custom item renderer when it is no longer used by the Menu.
recycler.reset = (itemRenderer:LayoutGroupItemRenderer, state:MenuItemState) -> {
var label = cast(itemRenderer.getChildByName("label"), Label);
var loader = cast(itemRenderer.getChildByName("loader"), AssetLoader);
label.text = "";
loader.source = null;
};Warning: A
DisplayObjectRecyclerwithout areset()method could potentially cause memory leaks or other unexpected behavior, if the same data needs to be used again later.
Styles
A number of styles may be customized on a Menu component, including an optional background skin and the layout.
Background skin
Optionally give the menu a background using the backgroundSkin property. The following example sets it to a RectangleSkin instance.
var skin = new RectangleSkin();
skin.border = SolidColor(1.0, 0x999999);
skin.fill = SolidColor(0xcccccc);
skin.width = 16.0;
skin.height = 16.0;
menu.backgroundSkin = skin;The border and fill properties of the RectangleSkin are used to adjust its appearance. They support a variety of values — from solid colors to gradients to bitmaps.
The menu automatically calculates its preferred size based on the initial dimensions of its background skin (accounting for some other factors too, like the layout and scroll bars), so it's important to set a skin's width and height properties to appropriate values to use in this calculation.
See Skinning with common shapes for more details about how to use
RectangleSkinwith theLineStyleandFillStyleenums that change its border and fill appearance.
Layout
Set the menu's layout property to change how its children are positioned and sized. By default, a menu uses VerticalListLayout, but it may be changed to a different layout, if desired.
menu.layout = new HorizontalListLayout();The example above uses HorizontalListLayout, but a number of different layouts are available in Feathers UI, and it's also possible to create custom layouts.