Tutorial: Defered Creation of UI Elements

Defered Creation of UI Elements

Creating UI elements is an expensive operation on the HTML DOM. Therefore, it is advisable to only create the elements you need initially and defer the creation of other elements to a later point.

Here are some ways of how to defer element creation.

Repeating Elements

The Repeater element spawns new elements from a template dynamically and places them in its parent container.

The property model accepts a ListModel element, which is described in detail in List Models, but for simplicity, the model may also be a plain number.

Document {

    Repeater {
        // passing a number implicitly creates a simple list model
        model: 100
        delegate: template Label {
            text: "Label #" + modelData.index
        }
    }

}

Always remember to mark the delegate with the template keyword.

The repeater can also destroy spawned elements dynamically, based on changes of the model.

require "shellfish/ui";  // required for Slider

Document {

    Slider {
        id: amountSlider

        fillWidth: true
        minValue: 1
        maxValue: 100
        value: 3
    }

    Repeater {
        model: Math.round(amountSlider.value)
        delegate: template Label {
            text: "Label #" + modelData.index
        }
    }

}

Note that since the repeater itself is an abstract element and creates the elements in its parent container dynamically, they will be placed after sibling elements of the repeater.

Document {

    Repeater {
        model: 3
        delegate: template Label {
            text: "Label #" + modelData.index
        }
    }

    Label {
        text: "I should be the last label (but I am not)."
    }

}

If you want to avoid this, put the repeater in a box.

Document {

    Box {
        Repeater {
            model: 3
            delegate: template Label {
                text: "Label #" + modelData.index
            }
        }
    }

    Label {
        text: "I am the last label."
    }

}

List of Elements

If you are spawning a lot of elements, the Repeater element might not be a good choice as it creates all of its elements at once, even if they are out of view.

The ListView element, on the other hand, is specialized in dynamically creating elements as they come into view, and destroying (or recycling) them as they disappear from view. This lets you handle thousands of elements.

The list view layouts its items in a grid. All items are of the same size, which is specified by the properties cellWidth and cellHeight.

The property scrollbars controls if the list view should use native scrollbars. By default its value is false.

require "shellfish/ui";  // required for Slider and theme

Document {

    Slider {
        id: amountSlider

        fillWidth: true
        minValue: 1
        maxValue: 100
        value: 3
    }

    ListView {
        fillWidth: true
        height: 300
        cellWidth: 100
        cellHeight: theme.fontSizeMedium
        scrollbars: true

        model: Math.round(amountSlider.value)
        delegate: template Label {
            text: "Label #" + modelData.index
        }
    }

}

Changing the Orientation

By default, the items are laid out vertically, i.e. the view will scroll vertically. By using the property orientation, you may change the orientation.

require "shellfish/ui";  // required for Slider and theme

Document {

    Slider {
        id: amountSlider

        fillWidth: true
        minValue: 1
        maxValue: 100
        value: 3
    }

    ListView {
        fillWidth: true
        height: 300
        cellWidth: 100
        cellHeight: theme.fontSizeMedium
        orientation: "horizontal"
        scrollbars: true

        model: Math.round(amountSlider.value)
        delegate: template Label {
            text: "Label #" + modelData.index
        }
    }

}

Viewing as List

If you want to display a real list with one item per row, simply set the cellWidth to at least the full width of the list view.

require "shellfish/ui";

Document {

    ListView {
        fillWidth: true
        height: 300
        cellWidth: bboxWidth
        cellHeight: theme.fontSizeMedium
        scrollbars: true

        model: 1000
        delegate: template Label {
            text: "Label #" + modelData.index
        }
    }

}

Attention:

In order to be fast even with thousands of elements, the list view recycles elements that went out of view instead of creating new ones (which would be expensive on the HTML DOM).

Because of this, always make sure that you reset the state of the delegates when the visible property changes to true. Otherwise you would start to see some artifacts of outdated data.

The Cache Margin

For a smoother scrolling experience, the list view is able to create items before they come into view and keep items around after they went out of view.

The property cacheMargin specifies the size of the margin area outside of the visible viewport within which items are created or kept alive. This means, the cache margin area is treated as if it was part of the visible viewport.

require "shellfish/ui";

Document {

    ListView {
        fillWidth: true
        height: 300
        cellWidth: bboxWidth
        cellHeight: theme.fontSizeMedium
        cacheMargin: 200
        scrollbars: true

        model: 1000
        delegate: template Label {
            text: "Label #" + modelData.index
        }
    }

}

Loading Elements Dynamically

The Loader element from shellfish/ui is a box which loads its content from a template or file dynamically.

Use the property sourceTemplate to specify a template.

require "shellfish/ui";  // required for Loader

Document {

    Loader {           
        sourceTemplate: template Box {
            
            Repeater {
                model: 100
                delegate: template Label {
                    text: "Label #" + modelData.index
                }
            }
            
        }
        
    }

}

Alternatively, you may use the property source to load content from another Shui file.

// Content.shui

Box {
    Repeater {
        model: 100
        delegate: template Label {
            text: "Label #" + modelData.index
        }
    }
}
// main.shui

require "shellfish/ui";

Document {

    Loader {
        source: "Content.shui"
    }

}

Once the content has been loaded, the property item refers to the root element of the loaded content.