Tuesday, January 29, 2013

Planning Poker for BB10

Check out my new app, Planning Poker for BlackBerry 10. It's created using Qt and Cascades/QML. The source code, which is released under the Apache License v2.0, is available from the project page on Bitbucket.

The application features a clean, simple and Ad-free user interface for your agile planning sessions. Simply swipe between cards or pinch to pick one fast. Double tap to hide/unhide. Supports light and dark card theme.



Thursday, January 17, 2013

Informative list views in Cascades (BB10)

This post gives an example how you can present the user some informative text or controls when a list view is empty.

Typical scenarios where this may be desirable are:
  • When populating a list view with data from a network service.
    • Show a Fetch data button if the list view is empty.
    • Present any error that might occur while downloading/processing the data
  • Showing search result in a list view.
    • Present a No items found text to the user if the list view is empty.
In the example application I'm using an ArrayDataModel, but it should work for QListDataModel and GroupDataModel too. Unfortunately, it will not work with XmlDataModel because the class doesn't expose any function to query if the model is empty (at least not easily).

All of the data models mentioned above provides an isEmpty() function which can be used to check if the data model contain any items or not.

The example should speaks for itself, so without any further ado, here's the code:

import bb.cascades 1.0
Page {

    Container {
        layout: DockLayout {}

        ListView {
            dataModel: ArrayDataModel {
                id: model

                // Bindable property which tells if the
                // model is empty or not
                property bool empty: true

                // Populates model with dummy data
                function populate() {
                    for (var i = 0; i < 10; i++) {
                        append({'title' : 'Item #' + i})
                    }
                }

                // The following signal handlers updates
                // the empty property to correct state
                onItemAdded: empty = isEmpty()
                onItemRemoved: empty = isEmpty()
                onItemUpdated: empty = isEmpty()

                // You might see an 'unknown signal' error
                // in the QML-editor, guess it's a SDK bug.
                onItemsChanged: empty = isEmpty()
            }
        }

        // Only visible when data model is empty.
        // This should be declared after the ListView
        // to prevent the ListView from consuming touch input etc
        // Setting ListView's visible property to !model.empty will
        // also work.
        Label {
            visible: model.empty //model.isEmpty() will not work
            horizontalAlignment: HorizontalAlignment.Fill
            verticalAlignment: VerticalAlignment.Center

            text: qsTr("No entries yet\n\nPlease populate data model")
            textStyle.base: SystemDefaults.TextStyles.BigText
            textStyle.textAlign: TextAlign.Center
            multiline: true
        }

    }

    actions: [
        // Action to populate/clear data model depending on state.
        // Same here, model.isEmpty() will not behave as expected.
        ActionItem {
            title: model.empty ? qsTr("Populate") : qsTr("Clear")
            onTriggered: model.empty ? model.populate() : model.clear()
            ActionBar.placement: ActionBarPlacement.OnBar
        }
    ]
}


Screenshots from the example application

A work-around is required because isEmpty() is only an invokable function in QML, and not a bindable property. The example will not behave as expected if model.isEmpty() is set as a value for the Label's visible property. The isEmpty() function is only called once when the Label is created and will never be re-evaluated again since it's not a bindable property (hint to Cascades API developers).

Please feel free to add other ways to accomplish this in the comments section.