How to use the CascadeListView component

The CascadeListView class renders a button that, when triggered, opens a series of cascading list views displayed as pop-ups. Each ListView displays a single branch from a hierarchical data collection, and the user drills down into the data by selecting an item from each list 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 CascadeListView control, and add it to the display list.

var cascadeListView = new CascadeListView();
addChild(cascadeListView);

Data provider

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

var collection = new ArrayHierarchicalCollection([
    {
        text: "Canada",
        children: [
            {
                text: "Ontario",
                children: [
                    {text: "Ottawa"},
                    {text: "Toronto"},
                ]
            },
            {
                text: "Quebec",
                children: [
                    {text: "Montreal"},
                    {text: "Quebec City"},
                ]
            }
        ]
    },
    {
        text: "United Kingdom",
        children: [
            {text: "Bristol"},
            {text: "London"},
            {text: "Manchester"},
            {text: "Newcastle"},
        ]
    },
    {
        text: "United States",
        children: [
            {
                text: "California",
                children: [
                    {text: "Los Angeles"},
                    {text: "Sacramento"},
                    {text: "San Diego"},
                    {text: "San Francisco"},
                ]
            },
            {
                text: "New York",
                children: [
                    {text: "Albany"},
                    {text: "Buffalo"},
                    {text: "New York City"},
                    {text: "Rochester"},
                ]
            },
            {
                text: "Texas",
                children: [
                    {text: "Austin"},
                    {text: "Dallas"},
                    {text: "El Paso"},
                    {text: "Houston"},
                ]
            },
        ]
    }
]);
cascadeListView.dataProvider = collection;

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

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

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

cascadeListView.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.

cascadeListView.addEventListener(Event.CHANGE, cascadeListView_changeHandler);

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

function cascadeListView_changeHandler(event:Event):Void {
    var cascadeListView = cast(event.currentTarget, CascadeListView);
    trace("CascadeListView selectedItem change: " + cascadeListView.selectedItem.text);
}

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

function cascadeListView_changeHandler(event:Event):Void {
    var cascadeListView = cast(event.currentTarget, CascadeListView);
    trace("CascadeListView selectedLocation change: " + cascadeListView.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];
cascadeListView.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];
cascadeListView.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 ListView or CascadeListView. In other words, a ListView displayed by CascadeListView typically contains multiple item renderers — with each one rendering a different item from the collection.

Feathers UI provides a default ItemRenderer class, which can display data in many different ways that cover a variety of common use-cases. However, components like CascadeListView also support custom item renderers, which allow developers to render the list 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 ItemRenderer 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 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.

cascadeListView.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:ListViewItemState) -> {
    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 ListViewItemState object. ListViewItemState 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. Obviously, we'll need an itemToText() function to populate the text value from the name field.

cascadeListView.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 CascadeListView.

recycler.reset = (itemRenderer:LayoutGroupItemRenderer, state:ListViewItemState) -> {
    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 CascadeListView component, including styles on the button and the list views.

Button

The button in a CascadeListView 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 CascadeListView.

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

Style button globally

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

styleProvider.setStyleFunction(
    Button,
    CascadeListView.CHILD_VARIANT_BUTTON,
    setCascadeListView_Button_Styles);

The function should use the following signature.

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

Style the button in a specific CascadeListView

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

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

ListView

The list views in a CascadeListView component are of type ListView. Their appearance may be customized globally in a theme, or they may be customized outside of a theme on an specific, individual CascadeListView.

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

Style list view globally

Use the CascadeListView.CHILD_VARIANT_LIST_VIEW constant in a theme to provide a function that globally styles the list views in all CascadeListView components.

styleProvider.setStyleFunction(
    ListView,
    CascadeListView.CHILD_VARIANT_LIST_VIEW,
    setCascadeListView_ListView_Styles);

The function should use the following signature.

function setCascadeListView_ListView_Styles(listView:ListView):Void {
    // ... set styles here
}

Style the list views in a specific CascadeListView

The listViewFactory property may be used to customize the creation of an individual list view.

cascadeListView.listViewFactory = () -> {
    var listView = new ListView();
    // ... set styles here
    return listView;
};