How to use the PopUpTreeView component

The PopUpTreeView class displays a button, that when triggered, renders the items from a hierarchical data collection in a pop-up tree view.

⚠️ Beta Notice: This component is still quite new. Some APIs may go through minor changes in upcoming releases.

The Basics

Start by creating a PopUpTreeView control, and add it to the display list.

var popUpTreeView = new PopUpTreeView();
addChild(popUpTreeView);

Data provider

To render some data in the tree view, pass in a hierarchical collection that contains an object for each row.

var collection = new ArrayHierarchicalCollection([
    {
        text: "Node 1",
        children: [
            {
                text: "Node 1A",
                children: [
                    { text: "Node 1A-I" },
                    { text: "Node 1A-II" }
                ]
            },
            { text: "Node 1B" },
        ]
    },
    { text: "Node 2" },
    {
        text: "Node 3",
        children: [
            { text: "Node 3A" },
            { text: "Node 3B" },
            { text: "Node 3C" }
        ]
    }
]);
popUpTreeView.dataProvider = collection;

Set the collection's itemToChildren() method to get the children from each branch that need to be rendered by the tree view.

collection.itemToChildren = (item:Dynamic) -> item.children;

Set the itemToText() method to get the text from each item to display from the collection.

popUpTreeView.itemToText = (item:Dynamic) -> item.text;

Items in the collection are not required to be anonymous structures, like {text: "Node 1B"} in the example above. Class instances are allowed too (and encouraged as a best practice; you should prefer classes over anonymous structures). If you use a class, be sure to update the item parameter's type in the itemToChildren and itemToText functions so that the compiler can catch any errors.

Selection

Add an event listener for Event.CHANGE to perform an action when the user selects a different item.

popUpTreeView.addEventListener(Event.CHANGE, popUpTreeView_changeHandler);

Check for the new value of the selectedItem property in the listener.

function popUpTreeView_changeHandler(event:Event):Void {
    var popUpTreeView = cast(event.currentTarget, PopUpTreeView);
    trace("PopUpTreeView selectedItem change: " + popUpTreeView.selectedItem.text);
}

Alternatively, the value of the selectedLocation property references the location of the items in the tree view's collection as an Array of integers.

function popUpTreeView_changeHandler(event:Event):Void {
    var popUpTreeView = cast(event.currentTarget, PopUpTreeView);
    trace("PopUpTreeView selectedLocation change: " + popUpTreeView.selectedLocation);
}

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];
popUpTreeView.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];
popUpTreeView.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 TreeView and PopUpTreeView. In other words, the TreeView displayed by a PopUpTreeView typically contains multiple item renderers — with each one rendering a different item from the collection.

Feathers UI provides a default HierarchicalItemRenderer class, which can display data in many different ways that cover a variety of common use-cases. However, components like PopUpTreeView also support custom item renderers, which allow developers to render the tree view'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 openedToggle = new ToggleButton();
    openedToggle.name = "openedToggle";
    itemRenderer.addChild(openedToggle);

    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 LayoutGroupItemRenderer class. In fact, a custom item renderer may be created from any OpenFL display object, including primitives like openfl.display.Sprite and all other Feathers UI components.

Pass the DisplayObjectRecycler to the itemRendererRecycler property.

popUpTreeView.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:TreeViewItemState) -> {
    var openedToggle = cast(itemRenderer.getChildByName("openedToggle"), ToggleButton);
    var label = cast(itemRenderer.getChildByName("label"), Label);
    var loader = cast(itemRenderer.getChildByName("loader"), AssetLoader);

    openedToggle.visible = state.branch;
    openedToggle.selected = state.branch && state.opened;
    label.text = state.text;
    loader.source = state.data.icon;
};

When the update() method is called, it receives the item renderer and an TreeViewItemState object. TreeViewItemState has a number of useful properties.

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. The values of branch and opened are used with a ToggleButton to display whether a branch is opened or not. Obviously, we'll need an itemToText() function to populate the text value from the name field.

popUpTreeView.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 PopUpTreeView.

recycler.reset = (itemRenderer:LayoutGroupItemRenderer, state:TreeViewItemState) -> {
    var label = cast(itemRenderer.getChildByName("label"), Label);
    var loader = cast(itemRenderer.getChildByName("loader"), AssetLoader);

    label.text = "";
    loader.source = null;
};

Warning: A DisplayObjectRecycler without a reset() 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 the sub-components of a PopUpTreeView component, including styles on the button and the tree view.

Button

The button in a PopUpTreeView component is of type Button. Its appearance may be customized globally in a theme, or it may be customized outside of a theme on an specific, individual PopUpTreeView.

See How to use the Button component for complete details about which styles are available for the button.

Style button globally

Use the PopUpTreeView.CHILD_VARIANT_BUTTON constant in a theme to provide a function that globally styles the buttons in all PopUpTreeView components.

styleProvider.setStyleFunction(
    Button,
    PopUpTreeView.CHILD_VARIANT_BUTTON,
    setPopUpTreeView_Button_Styles);

The function should use the following signature.

function setPopUpTreeView_Button_Styles(button:Button):Void {
    // ... set styles here
}

Style the button in a specific PopUpTreeView

The buttonFactory property may be used to customize the creation of an individual button.

popUpTreeView.buttonFactory = () -> {
    var button = new Button();
    // ... set styles here
    return button;
};

TreeView

The tree view in a PopUpTreeView component is of type TreeView. Its appearance may be customized globally in a theme, or it may be customized outside of a theme on an specific, individual PopUpTreeView.

See How to use the TreeView component for complete details about which styles are available for the tree view.

Style tree view globally

Use the PopUpTreeView.CHILD_VARIANT_TREE_VIEW constant in a theme to provide a function that globally styles the tree views in all PopUpTreeView components.

styleProvider.setStyleFunction(
    TreeView,
    PopUpTreeView.CHILD_VARIANT_TREE_VIEW,
    setPopUpTreeView_TreeView_Styles);

The function should use the following signature.

function setPopUpTreeView_TreeView_Styles(treeView:TreeView):Void {
    // ... set styles here
}

Style the tree view in a specific PopUpTreeView

The treeViewFactory property may be used to customize the creation of an individual tree view.

popUpTreeView.treeViewFactory = () -> {
    var treeView = new TreeView();
    // ... set styles here
    return treeView;
};